From 0762a8ab0c1cf9594cbc47eb876f0c7c48d821f4 Mon Sep 17 00:00:00 2001 From: Prajna Prayoga Date: Fri, 16 Feb 2024 20:32:34 +0700 Subject: [PATCH] feat: plan view, wip plan view model --- assets/icons/icon-portofolio.png | Bin 0 -> 1548 bytes assets/icons/icon-thumb.png | Bin 0 -> 1686 bytes lib/application/assets/path_assets.dart | 2 + .../component/button/button_view.dart | 5 +- .../component/goal_investing_view.dart | 6 +- .../component/numeric_pad/numeric_pad.dart | 25 +- lib/application/component/risk_profile.dart | 212 +++++++++++++++++ .../risk_profile/risk_profile_view.dart | 12 +- lib/features/bottom_navigation_view.dart | 45 ++-- .../view/plan/plan_view.dart | 22 -- .../view/plan/view/plan_view.dart | 206 ++++++++++++++++ .../options_starting_invest.dart | 123 ++++++++++ .../result_options_product.dart | 154 ++++++++++++ .../result_total_product.dart | 221 ++++++++++++++++++ .../view/plan/view_model/plan_view_model.dart | 11 + 15 files changed, 989 insertions(+), 55 deletions(-) create mode 100644 assets/icons/icon-portofolio.png create mode 100644 assets/icons/icon-thumb.png create mode 100644 lib/application/component/risk_profile.dart delete mode 100644 lib/features/dashboard/dashboard_account/view/plan/plan_view.dart create mode 100644 lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart create mode 100644 lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/options_starting_invest.dart create mode 100644 lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_options_product.dart create mode 100644 lib/features/dashboard/dashboard_account/view/plan/view/step_invest_plan/result_total_product.dart create mode 100644 lib/features/dashboard/dashboard_account/view/plan/view_model/plan_view_model.dart diff --git a/assets/icons/icon-portofolio.png b/assets/icons/icon-portofolio.png new file mode 100644 index 0000000000000000000000000000000000000000..b446ff0a59a0e14a195cfdba0027da1ed3d2d794 GIT binary patch literal 1548 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 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