From 506f364b87a8c60ff89aa678d4dd33b104382112 Mon Sep 17 00:00:00 2001 From: Prajna Prayoga Date: Thu, 22 Feb 2024 16:27:15 +0700 Subject: [PATCH 1/4] fix: layout dashboard public --- .../dashboard_public/view/dashboard_public_view.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/features/dashboard/dashboard_public/view/dashboard_public_view.dart b/lib/features/dashboard/dashboard_public/view/dashboard_public_view.dart index c0d7346..bfd2f41 100644 --- a/lib/features/dashboard/dashboard_public/view/dashboard_public_view.dart +++ b/lib/features/dashboard/dashboard_public/view/dashboard_public_view.dart @@ -44,7 +44,7 @@ class DashboardPublicView extends StatelessWidget { return Scaffold( body: SingleChildScrollView( padding: const EdgeInsets.only( - top: 32.0, + top: 36.0, bottom: 8.0, left: 24.0, right: 24.0, @@ -59,6 +59,7 @@ class DashboardPublicView extends StatelessWidget { image: PathAssets.icon1, width: SizeConfig.width * .35, ), + SizedBox(height: SizeConfig.height * .03), Align( alignment: Alignment.center, heightFactor: 1, @@ -105,7 +106,7 @@ class DashboardPublicView extends StatelessWidget { provider.loginGoogle(context); }, ), - SizedBox(height: SizeConfig.height * .15), + SizedBox(height: SizeConfig.height * .07), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ -- 2.45.2 From d966108e9efa2aeddf96e4b3052800f515401377 Mon Sep 17 00:00:00 2001 From: Prajna Prayoga Date: Thu, 22 Feb 2024 17:01:56 +0700 Subject: [PATCH 2/4] fix: more detail redeem product --- .../view/portfolio/portfolio_detail_view.dart | 69 ++++++++++--------- .../redeem_product/view/redeem_product.dart | 7 +- .../view_model/redeem_product_view_model.dart | 2 + 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/lib/features/dashboard/dashboard_account/view/portfolio/portfolio_detail_view.dart b/lib/features/dashboard/dashboard_account/view/portfolio/portfolio_detail_view.dart index f53f129..6d5eb77 100644 --- a/lib/features/dashboard/dashboard_account/view/portfolio/portfolio_detail_view.dart +++ b/lib/features/dashboard/dashboard_account/view/portfolio/portfolio_detail_view.dart @@ -4,6 +4,7 @@ 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/component/text_title/text_title.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/portfolio/redeem_product/view/redeem_product.dart'; import 'package:cims_apps/features/dashboard/dashboard_account/view/portfolio/redeem_product/view_model/redeem_product_view_model.dart'; @@ -18,29 +19,33 @@ class PortfolioDetailView extends StatelessWidget { List listProduct = [ PortfolioProduct( name: 'Gemilang Dana Kas Maxima', - type: '', + type: 'Money Market', yield: 8.17, priceUnit: 2600.79, - funds: 6300000), + funds: 6300000, + totalUnit: 14520 + ), PortfolioProduct( name: 'Gemilang Dana Likuid', - type: '', + type: 'Sharia', yield: 6.42, priceUnit: 1600.79, - funds: 2340000), + funds: 2340000, + totalUnit: 232, + ), PortfolioProduct( name: 'Gemilang Income Fund', - type: '', + type: 'Bonds', yield: 8.17, priceUnit: 2600.79, - funds: 6300000) + funds: 6300000, + totalUnit: 2450, + ) ]; - return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => RedeemProductViewModel()) - ], + return ChangeNotifierProvider( + create: (context) => RedeemProductViewModel(), child: Scaffold( backgroundColor: Colors.white, body: SizedBox( @@ -100,19 +105,20 @@ class PortfolioDetailView extends StatelessWidget { ), const SizedBox(height: 24,), Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular(0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), - child: ListView( - padding: const EdgeInsets.all(24), - children: [ - cardPortfolio(context) - ], - ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: Consumer( + builder: (context, provider, child) { + return ListView( + padding: const EdgeInsets.all(24), + children: listProduct.asMap().entries.map((e) { + return cardPortfolio(context, e.value); + }).toList(), + ); + } ), ) ) @@ -125,7 +131,7 @@ class PortfolioDetailView extends StatelessWidget { ); } - Widget cardPortfolio(context) { + Widget cardPortfolio(context, PortfolioProduct product) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -155,19 +161,19 @@ class PortfolioDetailView extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const TextTitle(title: 'Gemilang Dana Kas Maxima', fontSize: 16,), + TextTitle(title: product.name ?? '', fontSize: 16,), const SizedBox(height: 4), Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), - color: ColorPalette.investTypeBgColor['Money Market']?.withOpacity(0.5) ?? Colors.white, - border: Border.all(width: 2, color: ColorPalette.investTypeColor['Money Market']?.withOpacity(0.4) ?? Colors.white) + color: ColorPalette.investTypeBgColor[product.type!]?.withOpacity(0.5) ?? Colors.white, + border: Border.all(width: 2, color: ColorPalette.investTypeColor[product.type!]?.withOpacity(0.4) ?? Colors.white) ), child: Text( - 'Money Market' ?? '', + product.type ?? '', style: TextStyle( - color: ColorPalette.investTypeColor['Money Market'], + color: ColorPalette.investTypeColor[product.type!], fontWeight: FontWeight.w600 ), ), @@ -187,8 +193,8 @@ class PortfolioDetailView extends StatelessWidget { rowDescription('Present Value', 'Rp2.660.706', fontWeight: FontWeight.w700), rowDescription('Investment Capital', 'Rp2.660.706'), rowDescription('Advantages', 'Rp2.660.706'), - rowDescription('Purchase Price', 'Rp1.500,57'), - rowDescription('Number of Units', '14.002'), + rowDescription('Purchase Price', NumberFormatter.numberCurrency(product.priceUnit, 'Rp ', 'id_ID')), + rowDescription('Number of Units', '${product.totalUnit ?? 0}'), ], ), const SizedBox(height: 16,), @@ -206,6 +212,7 @@ class PortfolioDetailView extends StatelessWidget { borderColor: ColorPalette.red600, textColor: ColorPalette.red600, onPressed: () { + Provider.of(context, listen: false).setProduct(product); showModalBottomSheet( context: context, isScrollControlled: true, diff --git a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/redeem_product.dart b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/redeem_product.dart index 57e2a43..f3bb550 100644 --- a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/redeem_product.dart +++ b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/redeem_product.dart @@ -42,8 +42,7 @@ class _RedeemProductState extends State { create: (context) => RedeemProductViewModel(), child: Consumer( builder: (context, provider, child) { - double amount = provider.getAmount ?? provider.getCurrentProduct.priceUnit! * provider.getCurrentProduct.totalUnit!; - amountController.text = NumberFormatter.numberCurrency(amount.toInt(), 'Rp ', 'id_ID', decimalDigits: 0); + amountController.text = NumberFormatter.numberCurrency(provider.getAmount!.toInt(), 'Rp ', 'id_ID', decimalDigits: 0); return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -76,8 +75,8 @@ class _RedeemProductState extends State { const SizedBox(height: 16), segmentAmount( context, - provider.getAmount ?? provider.getCurrentProduct.priceUnit! * (provider.getCurrentProduct.totalUnit! / 2.0), - provider.getUnit ?? provider.getCurrentProduct.totalUnit! / 2.0, + provider.getAmount!, + provider.getUnit!, (value) { provider.setUnit(value); }, diff --git a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view_model/redeem_product_view_model.dart b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view_model/redeem_product_view_model.dart index e150e1e..3665162 100644 --- a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view_model/redeem_product_view_model.dart +++ b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view_model/redeem_product_view_model.dart @@ -61,6 +61,8 @@ class RedeemProductViewModel extends ChangeNotifier { void setProduct(PortfolioProduct product) { currentProduct = product; + amount = product.priceUnit! * (product.totalUnit! / 2.0); + unit = (product.totalUnit! / 2.0); notifyListeners(); } -- 2.45.2 From 38837bd4f8edb7fc2c54a4ae259af28bedf5e77b Mon Sep 17 00:00:00 2001 From: Prajna Prayoga Date: Thu, 22 Feb 2024 18:13:54 +0700 Subject: [PATCH 3/4] feat: expandable text component --- .../expandable_widget/expandable_widget.dart | 119 ++++++++++++++++++ .../see_more_less_widget.dart | 74 +++++++++++ .../component/radio_agreement.dart | 28 +---- lib/application/component/risk_profile.dart | 47 +++---- .../subscribe/total_payment_view.dart | 3 +- .../view/plan/view/plan_view.dart | 23 ++-- 6 files changed, 233 insertions(+), 61 deletions(-) create mode 100644 lib/application/component/expandable_widget/expandable_widget.dart create mode 100644 lib/application/component/expandable_widget/see_more_less_widget.dart diff --git a/lib/application/component/expandable_widget/expandable_widget.dart b/lib/application/component/expandable_widget/expandable_widget.dart new file mode 100644 index 0000000..0b56609 --- /dev/null +++ b/lib/application/component/expandable_widget/expandable_widget.dart @@ -0,0 +1,119 @@ +import 'package:cims_apps/application/component/expandable_widget/see_more_less_widget.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:flutter/material.dart'; + +class ExpandableWidget extends StatelessWidget { + final String? content; + final double? fontSize; + final int maxLinesToShow; + final Alignment? alignmentMore; + final bool? hideTextMore; + final bool? hideIconMore; + + ExpandableWidget({ + super.key, + required this.content, + this.fontSize, + this.maxLinesToShow = 1, + this.alignmentMore, + this.hideTextMore = false, + this.hideIconMore = true, + }); + + ValueNotifier expanded = ValueNotifier(false); + + @override + Widget build(BuildContext context) { + final TextSpan textSpan = TextSpan( + text: content ?? "", + style: TextStyle( + fontSize: fontSize ?? 16.0, + color: ColorPalette.slate400, + ), + ); + + final TextPainter textPainter = TextPainter( + text: textSpan, + maxLines: expanded.value ? null : maxLinesToShow, + textDirection: TextDirection.ltr, + strutStyle: StrutStyle( + fontSize: fontSize ?? 16.0, + ) + ); + + textPainter.layout(maxWidth: SizeConfig.width); + + final int numberOfLines = textPainter.computeLineMetrics().length; + return ValueListenableBuilder( + valueListenable: expanded, + builder: (context, values, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (!expanded.value && numberOfLines >= maxLinesToShow) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + content ?? "", + maxLines: maxLinesToShow, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: fontSize ?? 16.0, + color: ColorPalette.slate400, + ), + ), + /* See More :: type 1 - See More | 2 - See Less */ + SeeMoreLessWidget( + textData: 'See More', + type: 1, + section: 1, + onSeeMoreLessTap: () { + expanded.value = true; + }, + alignment: alignmentMore, + hideIconMore: hideIconMore!, + hideTextMore: hideTextMore!, + ), + /* See More :: type 1 - See More | 2 - See Less */ + ], + ); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + content ?? "", + style: TextStyle( + fontSize: fontSize ?? 16.0, + color: ColorPalette.slate400, + ), + ), + if (expanded.value && numberOfLines >= maxLinesToShow) + /* See Less :: type 1 - See More | 2 - See Less */ + SeeMoreLessWidget( + textData: 'See Less', + type: 2, + section: 1, + onSeeMoreLessTap: () { + expanded.value = false; + }, + alignment: alignmentMore, + hideIconMore: hideIconMore!, + hideTextMore: hideTextMore!, + ), + /* See Less :: type 1 - See More | 2 - See Less */ + ], + ); + } + }, + ), + ], + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/application/component/expandable_widget/see_more_less_widget.dart b/lib/application/component/expandable_widget/see_more_less_widget.dart new file mode 100644 index 0000000..8c0760f --- /dev/null +++ b/lib/application/component/expandable_widget/see_more_less_widget.dart @@ -0,0 +1,74 @@ +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:flutter/material.dart'; + +class SeeMoreLessWidget extends StatelessWidget { + final String? textData; + final int? type; /* type 1 - See More | 2 - See Less */ + final Function? onSeeMoreLessTap; + final int? + section; /* 1: About the course | 2 - Who can take up this course? | 3 - Mentors | 4 - Course Video Reviews */ + final Alignment? alignment; + final bool hideTextMore; + final bool hideIconMore; + + const SeeMoreLessWidget({ + super.key, + required this.textData, + required this.type, + required this.onSeeMoreLessTap, + required this.section, + required this.alignment, + required this.hideTextMore, + required this.hideIconMore, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: alignment ?? Alignment.centerLeft, + child: InkWell( + onTap: () { + if (onSeeMoreLessTap != null) { + onSeeMoreLessTap!(); + } + }, + child: Text.rich( + softWrap: true, + style: const TextStyle( + color: ColorPalette.primary, + ), + textAlign: TextAlign.start, + TextSpan( + text: "", + children: [ + if(!hideTextMore) + TextSpan( + text: textData, + style: const TextStyle( + color: ColorPalette.primary, + decoration: TextDecoration.underline, + ), + ), + if(!hideTextMore && !hideIconMore) + const WidgetSpan( + child: SizedBox( + width: 3.0, + ), + ), + if(!hideIconMore) + WidgetSpan( + child: Icon( + (type == 1) + ? Icons.keyboard_arrow_down + : Icons.keyboard_arrow_up, + color: ColorPalette.slate300, + size: 28, + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/application/component/radio_agreement.dart b/lib/application/component/radio_agreement.dart index cecd678..0759255 100644 --- a/lib/application/component/radio_agreement.dart +++ b/lib/application/component/radio_agreement.dart @@ -1,3 +1,4 @@ +import 'package:cims_apps/application/component/expandable_widget/expandable_widget.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; import 'package:flutter/material.dart'; @@ -44,28 +45,11 @@ class RadioAgreement extends StatelessWidget { width: 12, ), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - desc, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: ColorPalette.slate400), - ), - GestureDetector( - onTap: () {}, - child: const Text( - 'Read More', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - decoration: TextDecoration.underline, - color: ColorPalette.primary), - )) - ], - )) + child: ExpandableWidget( + content: desc, + maxLinesToShow: 3, + ) + ) ], ), ); diff --git a/lib/application/component/risk_profile.dart b/lib/application/component/risk_profile.dart index a2a0cf7..8a94990 100644 --- a/lib/application/component/risk_profile.dart +++ b/lib/application/component/risk_profile.dart @@ -1,4 +1,5 @@ import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/component/expandable_widget/expandable_widget.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'; @@ -73,20 +74,20 @@ class RiskProfile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - padding: EdgeInsets.all(24), + padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( riskProfile.type, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 24, color: ColorPalette.white ), ), - SizedBox(height: 16,), - Text('Total Score :', + const SizedBox(height: 16,), + const Text('Total Score :', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, @@ -94,7 +95,7 @@ class RiskProfile extends StatelessWidget { ), ), Text('$totalScore', - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 28, color: ColorPalette.white @@ -107,20 +108,20 @@ class RiskProfile extends StatelessWidget { ), ), ), - SizedBox( + const SizedBox( height: 24, ), - Text( - riskProfile.desc, - style: TextStyle( - color: ColorPalette.slate500, - fontSize: 16 - ) + ExpandableWidget( + content: riskProfile.desc, + hideTextMore: true, + hideIconMore: false, + maxLinesToShow: 4, + alignmentMore: Alignment.center, ), - SizedBox( + const SizedBox( height: 24, ), - Text( + const Text( 'Suitable Product', style: TextStyle( color: ColorPalette.slate800, @@ -128,7 +129,7 @@ class RiskProfile extends StatelessWidget { fontSize: 18 ), ), - SizedBox( + const SizedBox( height: 16, ), rowSuitableProduct ? @@ -137,7 +138,7 @@ class RiskProfile extends StatelessWidget { return Expanded( child: Container( margin: EdgeInsets.only(left: e.key != 0 ? 12 : 0), - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: ColorPalette.slate200), borderRadius: BorderRadius.circular(6) @@ -146,18 +147,18 @@ class RiskProfile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.all(8), + padding: const 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( + const SizedBox( height: 12, ), Text(e.value['desc'], - style: TextStyle( + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: ColorPalette.slate800 @@ -173,7 +174,7 @@ class RiskProfile extends StatelessWidget { runSpacing: 16, children: riskProfile.suitableProduct.map((e) { return Container( - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), border: Border.all(color: ColorPalette.slate200), @@ -181,7 +182,7 @@ class RiskProfile extends StatelessWidget { child: Row( children: [ Container( - padding: EdgeInsets.all(8), + padding: const EdgeInsets.all(8), alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, @@ -189,12 +190,12 @@ class RiskProfile extends StatelessWidget { ), child: Image.asset(e['icon'], width: SizeConfig.width * 0.07, color: riskProfile.color) ), - SizedBox( + const SizedBox( width: 12, ), Expanded( child: Text(e['desc'], - style: TextStyle( + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: ColorPalette.slate800 diff --git a/lib/application/component/subscribe/total_payment_view.dart b/lib/application/component/subscribe/total_payment_view.dart index a8ae096..e594d46 100644 --- a/lib/application/component/subscribe/total_payment_view.dart +++ b/lib/application/component/subscribe/total_payment_view.dart @@ -139,9 +139,8 @@ class TotalPaymentView extends StatelessWidget { ), RadioAgreement( isAgree: isAgree, - desc: '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. Read More', + desc: '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.', onTap: () { - print('gagaga'); onTapAgree(); }, ), 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 index b373824..1ed5d1a 100644 --- a/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart +++ b/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart @@ -1,12 +1,8 @@ -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/subscribe/goal_investing_view.dart'; import 'package:cims_apps/application/component/subscribe/input_investment_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'; @@ -46,33 +42,32 @@ class _PlanViewState extends State { appBar: CustomAppBar( height: SizeConfig.height * 0.08, title: 'Investment Plan', - leading: SizedBox(), + leading: const SizedBox(), ), body: SingleChildScrollView( - padding: EdgeInsets.all(24), + padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - RiskProfile( + const RiskProfile( totalScore: 26, rowSuitableProduct: true ), - SizedBox( + const SizedBox( height: 32, ), - Text('Your Goal in Investing', + const Text('Your Goal in Investing', style: TextStyle( fontWeight: FontWeight.w700, color: ColorPalette.slate800, fontSize: 18 ), ), - SizedBox( + const SizedBox( height: 24, ), GoalInvestingView( onListSelected: (p0) { - print(p0); showModalBottomSheet( context: context, isScrollControlled: true, @@ -103,7 +98,7 @@ class _PlanViewState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("It's time to invest", + const Text("It's time to invest", style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600 @@ -113,12 +108,12 @@ class _PlanViewState extends State { onTap: () { Navigator.pop(context); }, - child: Icon(Icons.close_rounded) + child: const Icon(Icons.close_rounded) ) ], ), ), - Divider(color: ColorPalette.slate200, height: 1), + const Divider(color: ColorPalette.slate200, height: 1), InputInvestmentView( selectedPlan: text, nextMove: (value) { -- 2.45.2 From d79959c47fdc9d198d0deba46fc9eb3f16fea1e9 Mon Sep 17 00:00:00 2001 From: Prajna Prayoga Date: Fri, 23 Feb 2024 14:56:06 +0700 Subject: [PATCH 4/4] fix: validation input investment --- .../subscribe/input_investment_view.dart | 147 +++++++++++------- .../view/plan/view/plan_view.dart | 7 +- .../redeem_product/view/change_amount.dart | 120 ++++---------- .../step_subscribe/select_goal_investing.dart | 63 ++++---- 4 files changed, 161 insertions(+), 176 deletions(-) diff --git a/lib/application/component/subscribe/input_investment_view.dart b/lib/application/component/subscribe/input_investment_view.dart index 1dd8a22..ad553bb 100644 --- a/lib/application/component/subscribe/input_investment_view.dart +++ b/lib/application/component/subscribe/input_investment_view.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cims_apps/application/component/button/button_view.dart'; import 'package:cims_apps/application/component/numeric_pad/numeric_pad.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; @@ -6,9 +8,13 @@ import 'package:cims_apps/core/utils/size_config.dart'; import 'package:flutter/material.dart'; class InputInvestmentView extends StatefulWidget { - final String selectedPlan; + final String? currentPlan; + final void Function()? changePlan; + final int? currentPrice; + final int? minimumPrice; + final int? maximumPrice; final void Function(String value) nextMove; - const InputInvestmentView({super.key, required this.selectedPlan, required this.nextMove}); + const InputInvestmentView({super.key, required this.nextMove, this.currentPlan, this.minimumPrice, this.maximumPrice, this.currentPrice, this.changePlan}); @override State createState() => _InputInvestmentViewState(); @@ -17,10 +23,35 @@ class InputInvestmentView extends StatefulWidget { class _InputInvestmentViewState extends State { TextEditingController inputController = TextEditingController(); + void validationInputValue(String currentValue) { + currentValue = currentValue.replaceAll('Rp ', '').replaceAll('.', ''); + if(currentValue.isEmpty){ + currentValue = '0'; + } + double parseValue = double.parse(currentValue); + if(widget.minimumPrice != null){ + if(parseValue <= widget.minimumPrice!){ + parseValue = widget.minimumPrice!.toDouble(); + } + } + + if(widget.maximumPrice != null){ + if(parseValue >= widget.maximumPrice!){ + parseValue = widget.maximumPrice!.toDouble(); + } + } + + inputController.text = NumberFormatter.numberCurrency(parseValue, 'Rp ', 'id_ID', decimalDigits: 0); + } + @override void initState() { // TODO: implement initState - inputController.text = 'Rp 0'; + if(widget.currentPrice != null){ + inputController.text = NumberFormatter.numberCurrency(widget.currentPrice, 'Rp ', 'id_ID', decimalDigits: 0); + }else{ + inputController.text = 'Rp 0'; + } super.initState(); } @@ -41,55 +72,53 @@ class _InputInvestmentViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 16), Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(widget.selectedPlan, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, + if(widget.currentPlan != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(widget.currentPlan ?? '', + style: const 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 - ), - ) - ], - ) - ], - ), + InkWell( + borderRadius: BorderRadius.circular(16), + onTap: widget.changePlan, + child: const 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( + style: const 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); - } + validationInputValue(value); }, - decoration: InputDecoration( + decoration: const InputDecoration( enabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: ColorPalette.primary, @@ -98,30 +127,36 @@ class _InputInvestmentViewState extends State { ) ), ), - SizedBox(height: 12), - Text('Minimum Budget Rp1,000,000', - style: TextStyle( - color: ColorPalette.slate400, - fontSize: 16, - fontWeight: FontWeight.w600 + const SizedBox(height: 12), + if(widget.minimumPrice != null) + Text('Minimum ${NumberFormatter.numberCurrency(widget.minimumPrice, 'Rp ', 'id_ID')}', + style: const TextStyle( + color: ColorPalette.slate400, + fontSize: 16, + fontWeight: FontWeight.w600 + ), ), - ), - SizedBox(height: 16), + if(widget.maximumPrice != null) + Text('Maximum ${NumberFormatter.numberCurrency(widget.maximumPrice, 'Rp ', 'id_ID')}', + style: const TextStyle( + color: ColorPalette.slate400, + fontSize: 16, + fontWeight: FontWeight.w600 + ), + ), + const SizedBox(height: 16), NumericPad(onNumberSelected: (p0) { - String checkIsZeroInput = inputController.text.replaceAll('Rp ', '').replaceAll(',', ''); - String getNumeric = p0; + String currentValue = inputController.text; if(p0.isNotEmpty){ - if(checkIsZeroInput != '0'){ - getNumeric = checkIsZeroInput + getNumeric; + if(currentValue != '0'){ + currentValue = currentValue + p0; } }else{ - getNumeric = checkIsZeroInput.substring(0, checkIsZeroInput.length - 1); + currentValue = currentValue.substring(0, currentValue.length - 1); } - String formatNumeric = NumberFormatter.numberCurrency( - double.parse(getNumeric), 'Rp ', 'id_ID', decimalDigits: 0).replaceAll('.', ','); - inputController.text = formatNumeric; + validationInputValue(currentValue); }), - SizedBox(height: 8), + const SizedBox(height: 24), ButtonView( name: 'Next', onPressed: () { @@ -129,7 +164,7 @@ class _InputInvestmentViewState extends State { }, width: SizeConfig.width, heightWrapContent: true, - contentPadding: EdgeInsets.symmetric(vertical: 16), + contentPadding: const EdgeInsets.symmetric(vertical: 16), marginVertical: 0, ) ], @@ -137,6 +172,6 @@ class _InputInvestmentViewState extends State { ), ], ), - );; + ); } } 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 index 1ed5d1a..149ffbd 100644 --- a/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart +++ b/lib/features/dashboard/dashboard_account/view/plan/view/plan_view.dart @@ -115,10 +115,13 @@ class _PlanViewState extends State { ), const Divider(color: ColorPalette.slate200, height: 1), InputInvestmentView( - selectedPlan: text, + currentPlan: text, + changePlan: () { + Navigator.pop(context); + }, nextMove: (value) { Navigator.pop(context); - int formatIntParse = int.parse(value.replaceAll('Rp ', '').replaceAll(',', '')); + int formatIntParse = int.parse(value.replaceAll('Rp ', '').replaceAll('.', '')); showModalBottomSheet(context: context, builder: (context) => OptionsStartingInvest(totalInvest: formatIntParse)); }, ), diff --git a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/change_amount.dart b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/change_amount.dart index e5d5a0a..13431c9 100644 --- a/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/change_amount.dart +++ b/lib/features/dashboard/dashboard_account/view/portfolio/redeem_product/view/change_amount.dart @@ -4,6 +4,7 @@ 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/component/numeric_pad/numeric_pad.dart'; +import 'package:cims_apps/application/component/subscribe/input_investment_view.dart'; import 'package:cims_apps/application/component/text_title/text_title.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; import 'package:cims_apps/core/utils/number_formatter.dart'; @@ -82,98 +83,33 @@ class _ChangeAmountState extends State { ), ), const Divider(height: 1, color: ColorPalette.slate200,), - Padding( - padding: EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - cardProduct(), - SizedBox(height: 24), - TextField( - controller: amountController, - textAlign: TextAlign.center, - style: const 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){ - amountController.text = NumberFormatter.numberCurrency(parseValue, 'Rp ', 'id_ID', decimalDigits: 0); - }else{ - amountController.text = NumberFormatter.numberCurrency(0, 'Rp ', 'id_ID', decimalDigits: 0); - } - }, - decoration: const InputDecoration( - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: ColorPalette.primary, - width: 2 - ), - ) - ), - ), - SizedBox(height: 12), - Text( - 'Min Redeem: ${(NumberFormatter.numberCurrency(10000, 'Rp ', 'id_ID', decimalDigits: 0))}', - style: TextStyle( - color: ColorPalette.slate400, - fontWeight: FontWeight.w600, - ), - ), - Text( - 'Max Redeem: ${(NumberFormatter.numberCurrency((provider.getCurrentProduct.priceUnit! * provider.getCurrentProduct.totalUnit!).toInt(), 'Rp ', 'id_ID', decimalDigits: 0))}', - style: TextStyle( - color: ColorPalette.slate400, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 24), - NumericPad(onNumberSelected: (p0) { - String checkIsZeroInput = amountController.text.replaceAll('Rp ', '').replaceAll('.', ''); - String getNumeric = p0; - - if(p0.isNotEmpty){ - if(checkIsZeroInput != '0'){ - getNumeric = checkIsZeroInput + getNumeric; - } - }else{ - getNumeric = checkIsZeroInput.substring(0, checkIsZeroInput.length - 1); - } - if(getNumeric.isEmpty){ - getNumeric = '0'; - } - if(double.parse(getNumeric) >= provider.getCurrentProduct.priceUnit! * provider.getCurrentProduct.totalUnit!){ - getNumeric = (provider.getCurrentProduct.priceUnit! * provider.getCurrentProduct.totalUnit!).toString(); - } - String formatNumeric = NumberFormatter.numberCurrency( - double.parse(getNumeric).toInt(), 'Rp ', 'id_ID', decimalDigits: 0); - amountController.text = formatNumeric; - }), - const SizedBox(height: 24), - ButtonView( - name: 'Confirm', - textSize: 20, - marginVertical: 0, - onPressed: () { - String formatValueInput = amountController.text.replaceAll('Rp ', '').replaceAll('.', ''); - provider.setAmount(double.parse(formatValueInput)); - Navigator.pop(context); - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) { - return const RedeemProduct(); - }, - ); - }, - ) - ], - ), - ) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(24), + child: cardProduct() + ), + InputInvestmentView( + minimumPrice: (provider.getCurrentProduct.priceUnit! * 1).toInt(), + maximumPrice: (provider.getCurrentProduct.priceUnit! * provider.getCurrentProduct.totalUnit!).toInt(), + currentPrice: provider.getAmount!.toInt(), + nextMove: (value) { + String formatValueInput = value.replaceAll('Rp ', '').replaceAll('.', ''); + provider.setAmount(double.parse(formatValueInput)); + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return const RedeemProduct(); + }, + ); + }, + ) + ], + ), + SizedBox(height: 16,) ], ); } diff --git a/lib/features/dashboard/dashboard_account/view/product/view/step_subscribe/select_goal_investing.dart b/lib/features/dashboard/dashboard_account/view/product/view/step_subscribe/select_goal_investing.dart index f21532f..ab5cbe0 100644 --- a/lib/features/dashboard/dashboard_account/view/product/view/step_subscribe/select_goal_investing.dart +++ b/lib/features/dashboard/dashboard_account/view/product/view/step_subscribe/select_goal_investing.dart @@ -55,32 +55,43 @@ class SelectGoalInvesting extends StatelessWidget { create: (context) => ProductViewModel(), child: Consumer( builder: (context, provider, child) { - return InputInvestmentView( - selectedPlan: p0, - nextMove: (value) { - Navigator.pop(context); - int formatIntParse = int.parse(value.replaceAll('Rp ', '').replaceAll(',', '')); - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => - ChangeNotifierProvider( - create: (context) => ProductViewModel(), - child: Consumer( - builder: (context, provider, child) { - return TotalPaymentView( - listProduct: [ - provider.getSelectedProduct - ], - totalInvest: formatIntParse, - isAgree: provider.isAgree, - onTapAgree: provider.setAgree, - ); - } - ), - ) - ); - }, + return Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: InputInvestmentView( + currentPlan: p0, + changePlan: () { + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => SelectGoalInvesting(), + ); + }, + nextMove: (value) { + Navigator.pop(context); + int formatIntParse = int.parse(value.replaceAll('Rp ', '').replaceAll('.', '')); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => + ChangeNotifierProvider( + create: (context) => ProductViewModel(), + child: Consumer( + builder: (context, provider, child) { + return TotalPaymentView( + listProduct: [ + provider.getSelectedProduct + ], + totalInvest: formatIntParse, + isAgree: provider.isAgree, + onTapAgree: provider.setAgree, + ); + } + ), + ) + ); + }, + ), ); } ), -- 2.45.2