diff --git a/assets/icons/icon-portofolio.png b/assets/icons/icon-portofolio.png new file mode 100644 index 0000000..b446ff0 Binary files /dev/null and b/assets/icons/icon-portofolio.png differ diff --git a/assets/icons/icon-thumb.png b/assets/icons/icon-thumb.png new file mode 100644 index 0000000..0bac76a Binary files /dev/null and b/assets/icons/icon-thumb.png differ diff --git a/lib/application/assets/path_assets.dart b/lib/application/assets/path_assets.dart index f3fa70f..7298748 100644 --- a/lib/application/assets/path_assets.dart +++ b/lib/application/assets/path_assets.dart @@ -37,6 +37,8 @@ class PathAssets { static const String iconHouse = 'assets/icons/icon-house.png'; static const String iconToga = 'assets/icons/icon-toga.png'; static const String iconCreatePlan = 'assets/icons/icon-create-plan.png'; + static const String iconThumb = 'assets/icons/icon-thumb.png'; + static const String iconPortofolio = 'assets/icons/icon-portofolio.png'; /// IMAGE static const String imgSplashLogo = 'assets/images/splash-logo.png'; diff --git a/lib/application/component/button/button_view.dart b/lib/application/component/button/button_view.dart index 888dc89..676dd89 100644 --- a/lib/application/component/button/button_view.dart +++ b/lib/application/component/button/button_view.dart @@ -9,7 +9,7 @@ class ButtonView extends StatelessWidget { final double? height, width, widthSuffix, widthPrefix, marginVertical; final EdgeInsetsGeometry? contentPadding; final bool isSecondaryColor, isOutlined, heightWrapContent, disabled; - final Color? backgroundColor, textColor, borderColor; + final Color? backgroundColor, textColor, borderColor, disabledBgColor; final MainAxisAlignment? mainAxisAlignmentContent; // final _widthBtn = SizeConfig.screenWidth / 1.5; final _widthBtn = SizeConfig.width * .9; @@ -34,6 +34,7 @@ class ButtonView extends StatelessWidget { this.backgroundColor, this.borderColor, this.textColor, + this.disabledBgColor, this.textWeight = FontWeight.bold, this.textSize, this.textAlign = TextAlign.center, @@ -65,7 +66,7 @@ class ButtonView extends StatelessWidget { height: heightWrapContent ? null : height ?? _heightBtn, child: ElevatedButton( style: ElevatedButton.styleFrom( - disabledBackgroundColor: isOutlined ? Colors.white : color.surface, + disabledBackgroundColor: disabledBgColor ?? (isOutlined ? Colors.white : color.surface), padding: contentPadding, backgroundColor: backgroundColor ?? (isOutlined diff --git a/lib/application/component/goal_investing_view.dart b/lib/application/component/goal_investing_view.dart index 77f96fd..8ea6606 100644 --- a/lib/application/component/goal_investing_view.dart +++ b/lib/application/component/goal_investing_view.dart @@ -11,7 +11,8 @@ class GoalInvest { } class GoalInvestingView extends StatelessWidget { - const GoalInvestingView({super.key}); + final void Function(String) onListSelected; + const GoalInvestingView({super.key, required this.onListSelected}); @override Widget build(BuildContext context) { @@ -28,6 +29,9 @@ class GoalInvestingView extends StatelessWidget { return Padding( padding: EdgeInsets.only(top: e.key != 0 ? 16 : 0), child: ListTile( + onTap: () { + onListSelected(e.value.title); + }, shape: RoundedRectangleBorder( side: BorderSide(color: ColorPalette.slate200), borderRadius: BorderRadius.circular(14) diff --git a/lib/application/component/numeric_pad/numeric_pad.dart b/lib/application/component/numeric_pad/numeric_pad.dart index 797aa82..959d666 100644 --- a/lib/application/component/numeric_pad/numeric_pad.dart +++ b/lib/application/component/numeric_pad/numeric_pad.dart @@ -36,6 +36,18 @@ class NumericPad extends StatelessWidget { ], ), dividerGradient(true, Alignment.centerLeft, Alignment.centerRight), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + numberWidget('7'), + dividerGradient(false, Alignment.center, Alignment.center, fullColor: true), + numberWidget('8'), + dividerGradient(false, Alignment.center, Alignment.center, fullColor: true), + numberWidget('9') + ], + ), + dividerGradient(true, Alignment.centerLeft, Alignment.centerRight), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, @@ -54,7 +66,7 @@ class NumericPad extends StatelessWidget { Widget dividerGradient(bool isHorizontal, AlignmentGeometry gradientFrom, AlignmentGeometry gradientTo, {bool fullColor = false}) { return Container( width: isHorizontal ? SizeConfig.width : 1, - height: isHorizontal ? 1 : SizeConfig.height * 0.11, + height: isHorizontal ? 1 : SizeConfig.height * 0.097, decoration: BoxDecoration( gradient: LinearGradient( colors: [ @@ -100,9 +112,14 @@ class NumericPad extends StatelessWidget { Widget removeWidget() { return Expanded( - child: Icon( - Icons.highlight_remove, - size: 28, + child: GestureDetector( + onTap: () { + onNumberSelected(''); + }, + child: Icon( + Icons.highlight_remove, + size: 28, + ), ) ); } diff --git a/lib/application/component/risk_profile.dart b/lib/application/component/risk_profile.dart new file mode 100644 index 0000000..a2a0cf7 --- /dev/null +++ b/lib/application/component/risk_profile.dart @@ -0,0 +1,212 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/auth/registration/view/submission_data/risk_profile/risk_profile_view_model/risk_profile_view_model.dart'; +import 'package:flutter/material.dart'; + +class RiskProfile extends StatelessWidget { + final int totalScore; + final bool rowSuitableProduct; + const RiskProfile({super.key, required this.totalScore, required this.rowSuitableProduct}); + + @override + Widget build(BuildContext context) { + List listRiskProfileResult = [ + RiskProfileResult( + 'Conservative', + PathAssets.imgCat, + ColorPalette.green500, + 'Investors with a conservative risk profile are risk-averse or do not want to experience large losses. Therefore, mutual fund products that are suitable for conservative investors are products that have low risk and stable returns.', + [ + {'desc': 'Money Market Mutual Fund', 'icon': PathAssets.iconStrongBox}, + {'desc': 'Fixed Income Mutual Fund', 'icon': PathAssets.iconMoneyReceive}, + {'desc': 'Balanced Mutual Fund', 'icon': PathAssets.iconBalance}, + ] + ), + RiskProfileResult( + 'Moderate', + PathAssets.imgDeer, + ColorPalette.orange500, + 'Investors with a moderate risk profile are investors who are ready to accept moderate risk to get higher returns than conservative mutual fund products. Therefore, mutual fund products that are suitable for moderate investors are products that have moderate risk and higher returns than conservative mutual fund products.', + [ + {'desc': 'Fixed Income Mutual Fund', 'icon': PathAssets.iconMoneyReceive}, + {'desc': 'Balanced Mutual Fund', 'icon': PathAssets.iconBalance}, + ] + ), + RiskProfileResult( + 'Aggressive', + PathAssets.imgLion, + ColorPalette.purple500, + 'Investors with an aggressive risk profile are investors who are ready to accept high risks to get high returns. Therefore, mutual fund products that are suitable for aggressive investors are products that have high risk and high returns.', + [ + {'desc': 'Equity Mutual Fund', 'icon': PathAssets.iconCoins}, + {'desc': 'Aggressive Balanced Fund', 'icon': PathAssets.iconBalance}, + ] + ) + ]; + RiskProfileResult riskProfile; + if(totalScore <= 25){ + riskProfile = listRiskProfileResult[0]; + }else if(totalScore <= 50){ + riskProfile = listRiskProfileResult[1]; + }else{ + riskProfile = listRiskProfileResult[2]; + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: riskProfile.color, + image: DecorationImage(image: AssetImage(riskProfile.img), alignment: Alignment.centerRight), + boxShadow: [ + BoxShadow( + color: riskProfile.color.withOpacity(0.2), + blurRadius: 30 + ) + ] + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + riskProfile.type, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + color: ColorPalette.white + ), + ), + SizedBox(height: 16,), + Text('Total Score :', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: ColorPalette.white + ), + ), + Text('$totalScore', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 28, + color: ColorPalette.white + ), + ) + ], + ), + ), + ], + ), + ), + ), + SizedBox( + height: 24, + ), + Text( + riskProfile.desc, + style: TextStyle( + color: ColorPalette.slate500, + fontSize: 16 + ) + ), + SizedBox( + height: 24, + ), + Text( + 'Suitable Product', + style: TextStyle( + color: ColorPalette.slate800, + fontWeight: FontWeight.bold, + fontSize: 18 + ), + ), + SizedBox( + height: 16, + ), + rowSuitableProduct ? + Row( + children: riskProfile.suitableProduct.asMap().entries.map((e) { + return Expanded( + child: Container( + margin: EdgeInsets.only(left: e.key != 0 ? 12 : 0), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all(color: ColorPalette.slate200), + borderRadius: BorderRadius.circular(6) + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: riskProfile.color.withOpacity(0.1) + ), + child: Image.asset(e.value['icon'], width: SizeConfig.width * 0.07, color: riskProfile.color) + ), + SizedBox( + height: 12, + ), + Text(e.value['desc'], + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: ColorPalette.slate800 + ), + ) + ], + ), + ) + ); + }).toList(), + ) + : Wrap( + runSpacing: 16, + children: riskProfile.suitableProduct.map((e) { + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all(color: ColorPalette.slate200), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(8), + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: riskProfile.color.withOpacity(0.1) + ), + child: Image.asset(e['icon'], width: SizeConfig.width * 0.07, color: riskProfile.color) + ), + SizedBox( + width: 12, + ), + Expanded( + child: Text(e['desc'], + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: ColorPalette.slate800 + ), + ), + ) + ], + ), + ); + }).toList(), + ) + ], + ); + } +} diff --git a/lib/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart b/lib/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart index 9103416..ac62afc 100644 --- a/lib/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart +++ b/lib/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart @@ -22,7 +22,7 @@ class RiskProfileView extends StatelessWidget { title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BackButtonView(), + const BackButtonView(), const Text('Risk Profile', textAlign: TextAlign.center), SizedBox(width: SizeConfig.width * 0.1) ], @@ -33,11 +33,11 @@ class RiskProfileView extends StatelessWidget { body: Container( width: SizeConfig.width, height: SizeConfig.height, - padding: EdgeInsets.all(24), + padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Column( + const Column( children: [ ImageView(image: PathAssets.imgDataReport), SizedBox( @@ -66,7 +66,7 @@ class RiskProfileView extends StatelessWidget { ), Column( children: [ - Row( + const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ImageView( @@ -86,13 +86,13 @@ class RiskProfileView extends StatelessWidget { ) ], ), - SizedBox( + const SizedBox( height: 24, ), ButtonView( name: "Let's Start", onPressed: () { - routePush(context, page: QuestionView()); + routePush(context, page: const QuestionView()); }, marginVertical: 0, ) diff --git a/lib/features/bottom_navigation_view.dart b/lib/features/bottom_navigation_view.dart index ffe57f5..46cfca6 100644 --- a/lib/features/bottom_navigation_view.dart +++ b/lib/features/bottom_navigation_view.dart @@ -1,8 +1,10 @@ import 'package:cims_apps/application/theme/color_palette.dart'; import 'package:cims_apps/features/dashboard/dashboard_account/view/homepage/homepage_view.dart'; -import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/plan_view.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view/plan_view.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart'; import 'package:cims_apps/features/dashboard/dashboard_account/view/portfolio/portfolio_view.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class BottomNavigationView extends StatefulWidget { const BottomNavigationView({Key? key}) : super(key: key); @@ -48,25 +50,28 @@ class _BottomNavigationViewState extends State { ), ]; - return Scaffold( - body: listWidget[_selectedIndex], - bottomNavigationBar: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: BottomNavigationBar( - elevation: 0, - onTap: (value) { - setState(() { - _selectedIndex = value; - }); - }, - currentIndex: _selectedIndex, - items: listNavigation, - type: BottomNavigationBarType.fixed, - showUnselectedLabels: true, - selectedItemColor: ColorPalette.primary, - unselectedItemColor: Colors.black, - selectedLabelStyle: const TextStyle(color: ColorPalette.primary), - unselectedLabelStyle: const TextStyle(color: Colors.black), + return ChangeNotifierProvider( + create: (context) => PlanViewModel(), + child: Scaffold( + body: listWidget[_selectedIndex], + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: BottomNavigationBar( + elevation: 0, + onTap: (value) { + setState(() { + _selectedIndex = value; + }); + }, + currentIndex: _selectedIndex, + items: listNavigation, + type: BottomNavigationBarType.fixed, + showUnselectedLabels: true, + selectedItemColor: ColorPalette.primary, + unselectedItemColor: Colors.black, + selectedLabelStyle: const TextStyle(color: ColorPalette.primary), + unselectedLabelStyle: const TextStyle(color: Colors.black), + ), ), ), ); diff --git a/lib/features/dashboard/dashboard_account/view/plan/plan_view.dart b/lib/features/dashboard/dashboard_account/view/plan/plan_view.dart deleted file mode 100644 index 004462f..0000000 --- a/lib/features/dashboard/dashboard_account/view/plan/plan_view.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:cims_apps/application/component/custom_app_bar/custom_app_bar.dart'; -import 'package:cims_apps/application/component/goal_investing_view.dart'; -import 'package:flutter/material.dart'; - -class PlanView extends StatelessWidget { - const PlanView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar(height: 70, title: 'Investment Plan'), - body: SingleChildScrollView( - padding: EdgeInsets.all(24), - child: Column( - children: [ - GoalInvestingView() - ], - ), - ), - ); - } -} diff --git a/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart b/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart new file mode 100644 index 0000000..c5caad2 --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart @@ -0,0 +1,206 @@ +import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/component/custom_app_bar/custom_app_bar.dart'; +import 'package:cims_apps/application/component/goal_investing_view.dart'; +import 'package:cims_apps/application/component/numeric_pad/numeric_pad.dart'; +import 'package:cims_apps/application/component/risk_profile.dart'; +import 'package:cims_apps/application/component/text_form/text_form_view.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/number_formatter.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/options_starting_invest.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class PlanView extends StatefulWidget { + const PlanView({super.key}); + + @override + State createState() => _PlanViewState(); +} + +class _PlanViewState extends State { + TextEditingController inputController = TextEditingController(); + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + inputController.dispose(); + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + appBar: CustomAppBar(height: 70, title: 'Investment Plan'), + body: SingleChildScrollView( + padding: EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RiskProfile( + totalScore: 26, + rowSuitableProduct: true + ), + SizedBox( + height: 32, + ), + Text('Your Goal in Investing', + style: TextStyle( + fontWeight: FontWeight.w700, + color: ColorPalette.slate800, + fontSize: 18 + ), + ), + SizedBox( + height: 24, + ), + GoalInvestingView( + onListSelected: (p0) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return modalInvest(context, p0); + }, + ); + }, + ) + ], + ), + ), + ); + } + + Widget modalInvest(context, String text) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16) + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("It's time to invest", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600 + ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.close_rounded) + ) + ], + ), + ), + Divider(color: ColorPalette.slate200, height: 1), + Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(text, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + + ), + ), + Row( + children: [ + Icon(Icons.change_circle_outlined, color: ColorPalette.primary, size: 20), + SizedBox(width: 4), + Text('Change', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: ColorPalette.primary + ), + ) + ], + ) + ], + ), + TextField( + controller: inputController, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w600, + color: ColorPalette.slate800 + ), + keyboardType: TextInputType.number, + onChanged: (value) { + value = value.replaceAll('Rp ', '').replaceAll('.', ''); + double parseValue = double.parse(value); + if(value.isNotEmpty){ + inputController.text = NumberFormatter.numberCurrency(parseValue, 'Rp ', 'id_ID', decimalDigits: 0); + }else{ + inputController.text = NumberFormatter.numberCurrency(0, 'Rp ', 'id_ID', decimalDigits: 0); + } + }, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: ColorPalette.primary, + width: 2 + ), + ) + ), + ), + SizedBox(height: 12), + Text('Minimum Budget Rp1,000,000', + style: TextStyle( + color: ColorPalette.slate400, + fontSize: 16, + fontWeight: FontWeight.w600 + ), + ), + SizedBox(height: 16), + NumericPad(onNumberSelected: (p0) { + String checkIsZeroInput = inputController.text.replaceAll('Rp ', '').replaceAll(',', ''); + String getNumeric = p0; + if(p0.isNotEmpty){ + if(checkIsZeroInput != '0'){ + getNumeric = checkIsZeroInput + getNumeric; + } + }else{ + getNumeric = checkIsZeroInput.substring(0, checkIsZeroInput.length - 1); + } + String formatNumeric = NumberFormatter.numberCurrency( + double.parse(getNumeric), 'Rp ', 'id_ID', decimalDigits: 0).replaceAll('.', ','); + inputController.text = formatNumeric; + }), + SizedBox(height: 8), + ButtonView( + name: 'Next', + onPressed: () { + Navigator.pop(context); + int formatIntParse = int.parse(inputController.text.replaceAll('Rp ', '').replaceAll(',', '')); + showModalBottomSheet(context: context, builder: (context) => OptionsStartingInvest(totalInvest: formatIntParse)); + }, + width: SizeConfig.width, + heightWrapContent: true, + contentPadding: EdgeInsets.symmetric(vertical: 16), + marginVertical: 0, + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/options_starting_invest.dart b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/options_starting_invest.dart new file mode 100644 index 0000000..1047ecd --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/options_starting_invest.dart @@ -0,0 +1,123 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart'; +import 'package:flutter/material.dart'; + +class OptionsStartingInvest extends StatelessWidget { + final int totalInvest; + const OptionsStartingInvest({super.key, required this.totalInvest}); + + @override + Widget build(BuildContext context) { + List listOptions = [ + { + "title": "Results From Your Risk Profile", + "subtitle": "Start Investing from recommended products that suit you", + "iconImg": PathAssets.iconThumb, + "value": "risk profile" + }, + { + "title": "Create Portfolio", + "subtitle": "Choose a portfolio according to your investment goals", + "iconImg": PathAssets.iconPortofolio, + "value": "investment goals" + } + ]; + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16) + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Your options for starting to invest', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600 + ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.close_rounded), + ) + ], + ), + ), + SizedBox(height: 8), + ...listOptions.asMap().entries.map((e) { + return GestureDetector( + onTap: () { + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => ResultOptionsProduct(totalInvest: totalInvest)); + }, + child: Container( + margin: const EdgeInsets.only(left: 24, right: 24, bottom: 12), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: ColorPalette.slate200), + ), + child: Row( + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: ColorPalette.blue200.withOpacity(0.5), + borderRadius: BorderRadius.circular(8) + ), + child: Image.asset(e.value['iconImg'], width: 22), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(e.value['title'], + style: TextStyle( + fontWeight: FontWeight.w600, + color: ColorPalette.slate800, + fontSize: 16 + ), + ), + SizedBox(height: 4), + Text(e.value['subtitle'], + style: TextStyle( + color: ColorPalette.slate400 + ), + ) + ], + ), + ) + ], + ), + ), + Icon(Icons.keyboard_arrow_right_rounded, color: ColorPalette.slate400,) + ], + ), + ), + ); + }), + SizedBox(height: 16,) + ], + ), + ); + } +} diff --git a/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart new file mode 100644 index 0000000..348f751 --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart @@ -0,0 +1,154 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_total_product.dart'; +import 'package:flutter/material.dart'; + +class Product { + String name, type; + double totalPercent; + + Product(this.name, this.type, this.totalPercent); +} +class ResultOptionsProduct extends StatelessWidget { + final int totalInvest; + const ResultOptionsProduct({super.key, required this.totalInvest}); + + @override + Widget build(BuildContext context) { + List listProduct = [ + Product('Gemilang Dana Kas Maxima', 'Money Market', 0.7), + Product('Gemilang Dana Likuid', 'Bonds', 0.2), + Product('Gemilang Kas 2 Kelas A', 'Shares', 0.1) + ]; + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16) + ), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(Icons.arrow_back, color: ColorPalette.slate500), + Text('Results from your risk profile', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + color: ColorPalette.slate800 + ), + ), + const Icon(Icons.close_rounded, color: ColorPalette.slate400) + ], + ), + const SizedBox(height: 32), + SingleChildScrollView( + child: Column( + children: listProduct.asMap().entries.map((e) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: ColorPalette.slate200), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + const BoxShadow( + color: Color(0XFF1E293B0A) + ) + ] + ), + child: Column( + children: [ + Row( + children: [ + const ImageView(image: PathAssets.iconGoogle, width: 30,), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(e.value.name, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorPalette.slate800 + ), + ), + const SizedBox(height: 4,), + Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: ColorPalette.investTypeColor[e.value.type]!), + color: ColorPalette.investTypeBgColor[e.value.type], + borderRadius: BorderRadius.circular(40) + ), + child: Text(e.value.type, + style: TextStyle( + color: ColorPalette.investTypeColor[e.value.type], + fontWeight: FontWeight.w600 + ), + ), + ) + ], + ), + ), + Text('${(e.value.totalPercent * 100).toInt()} %', + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, + color: ColorPalette.slate800 + ), + ) + ], + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Divider(height: 1, color: ColorPalette.slate200), + ), + GestureDetector( + onTap: () { + }, + child: const Text('See More', + style: TextStyle( + color: ColorPalette.slate500, + fontWeight: FontWeight.w600, + ), + ), + ) + ], + ), + ); + }).toList(), + ), + ), + const SizedBox( + height: 16, + ), + ButtonView( + name: 'Next', + onPressed: () { + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => ResultTotalProduct(listProduct: listProduct, totalInvest: totalInvest) + ); + }, + width: SizeConfig.width, + heightWrapContent: true, + contentPadding: const EdgeInsets.symmetric(vertical: 16), + marginVertical: 0, + ) + ], + ), + ); + } +} diff --git a/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_total_product.dart b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_total_product.dart new file mode 100644 index 0000000..57c55a8 --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_total_product.dart @@ -0,0 +1,221 @@ +import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/number_formatter.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart'; +import 'package:flutter/material.dart'; + +class ResultTotalProduct extends StatelessWidget { + final int totalInvest; + final List listProduct; + const ResultTotalProduct({super.key, required this.listProduct, required this.totalInvest}); + + @override + Widget build(BuildContext context) { + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16) + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Your Investment Today', + style: TextStyle( + color: ColorPalette.slate800, + fontWeight: FontWeight.w600, + fontSize: 16 + ), + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close_rounded) + ) + ], + ), + ), + ...listProduct.asMap().entries.map((e) { + return Container( + padding: EdgeInsets.only(left: 16, right: 24, bottom: 16, top: 16), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: ColorPalette.investTypeColor[e.value.type]!, + width: 8, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Text(e.value.name, + style: TextStyle( + color: ColorPalette.slate400, + fontWeight: FontWeight.w600, + fontSize: 16 + ), + ) + ), + Expanded( + flex: 7, + child: Text( + NumberFormatter.numberCurrency(totalInvest / e.value.totalPercent, 'Rp ', 'id_ID'), + textAlign: TextAlign.end, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 18, + color: ColorPalette.slate800 + ), + ) + ) + ], + ), + ); + }).toList(), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Purchase Commission', + style: TextStyle( + color: ColorPalette.slate400, + fontWeight: FontWeight.w600, + fontSize: 16 + ), + ), + Text('Free', + style: TextStyle( + color: ColorPalette.primary, + fontSize: 18, + fontWeight: FontWeight.w700 + ), + ) + ], + ), + ), + SizedBox(height: 16,), + Container( + color: ColorPalette.slate200.withOpacity(0.5), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total', + style: TextStyle( + color: ColorPalette.slate400, + fontWeight: FontWeight.w600, + fontSize: 16 + ), + ), + Text(NumberFormatter.numberCurrency(totalInvest, 'Rp ', 'id_ID'), + textAlign: TextAlign.end, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 18, + color: ColorPalette.slate800 + ), + ) + ], + ), + ), + buttonAgreement(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total Payment', + style: TextStyle( + color: ColorPalette.slate400, + fontWeight: FontWeight.w600, + fontSize: 16 + ), + ), + Text(NumberFormatter.numberCurrency(totalInvest, 'Rp ', 'id_ID'), + textAlign: TextAlign.end, + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 18, + color: ColorPalette.slate800 + ), + ) + ], + ), + ), + ButtonView( + disabled: true, + name: 'Subscribe Now', + onPressed: () { + + }, + disabledBgColor: ColorPalette.slate200.withOpacity(0.5), + textColor: ColorPalette.slate400, + textWeight: FontWeight.w700, + textSize: 20, + backgroundColor: ColorPalette.primary, + ) + ], + ), + ); + } + + Widget buttonAgreement() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 24, + width: 24, + child: Radio( + value: false, + groupValue: false, + onChanged: (value) { + + }, + activeColor: ColorPalette.primary, + ), + ), + SizedBox(width: 12,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('I agree to buy the mutual fund on this page and have read and agreed to all the contents of the Prospectus and summary information and understand the risks of my investment decision.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: ColorPalette.slate400 + ), + ), + GestureDetector( + onTap: () { + + }, + child: Text('Read More', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + decoration: TextDecoration.underline, + color: ColorPalette.primary + ), + ) + ) + ], + ) + ) + ], + ), + ); + } +} diff --git a/lib/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart b/lib/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart new file mode 100644 index 0000000..634e624 --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart @@ -0,0 +1,11 @@ + +import 'package:flutter/material.dart'; + +class PlanViewModel extends ChangeNotifier { + bool isAgreement = false; + + void setAgreement() { + isAgreement = !isAgreement; + notifyListeners(); + } +} \ No newline at end of file