From a45c5b11a7aed29c43ee18381f0e8d15b4135183 Mon Sep 17 00:00:00 2001 From: Kima Date: Mon, 3 Jun 2024 20:15:47 +0200 Subject: [PATCH] progress in new goal planner --- lib/ui/mobile/goal_planner/goal_input.dart | 78 +++---- .../mobile/goal_planner/goal_track_popup.dart | 196 +++++++++++++++++- lib/ui/mobile/goal_planner/route_option.dart | 39 ++-- 3 files changed, 246 insertions(+), 67 deletions(-) diff --git a/lib/ui/mobile/goal_planner/goal_input.dart b/lib/ui/mobile/goal_planner/goal_input.dart index 98d1ada..b38bce0 100644 --- a/lib/ui/mobile/goal_planner/goal_input.dart +++ b/lib/ui/mobile/goal_planner/goal_input.dart @@ -61,45 +61,45 @@ class GoalInput extends StatelessWidget { ), ); }), - const SizedBox(height: 12.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: presets.map((e) { - final pv = (value * 10).round() / 10; - final selected = gradeToAvg(e) == pv; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(99.0), - color: - gradeColor(e, settings).withOpacity(selected ? 1.0 : 0.2), - border: Border.all(color: gradeColor(e, settings), width: 4), - ), - child: Material( - type: MaterialType.transparency, - child: InkWell( - borderRadius: BorderRadius.circular(99.0), - onTap: () => setValue(gradeToAvg(e)), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 2.0, horizontal: 24.0), - child: Text( - e.toString(), - style: TextStyle( - color: - selected ? Colors.white : gradeColor(e, settings), - fontWeight: FontWeight.bold, - fontSize: 24.0, - ), - ), - ), - ), - ), - ), - ); - }).toList(), - ) + // const SizedBox(height: 12.0), + // Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: presets.map((e) { + // final pv = (value * 10).round() / 10; + // final selected = gradeToAvg(e) == pv; + // return Padding( + // padding: const EdgeInsets.symmetric(horizontal: 12.0), + // child: Container( + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(99.0), + // color: + // gradeColor(e, settings).withOpacity(selected ? 1.0 : 0.2), + // border: Border.all(color: gradeColor(e, settings), width: 4), + // ), + // child: Material( + // type: MaterialType.transparency, + // child: InkWell( + // borderRadius: BorderRadius.circular(99.0), + // onTap: () => setValue(gradeToAvg(e)), + // child: Padding( + // padding: const EdgeInsets.symmetric( + // vertical: 2.0, horizontal: 24.0), + // child: Text( + // e.toString(), + // style: TextStyle( + // color: + // selected ? Colors.white : gradeColor(e, settings), + // fontWeight: FontWeight.bold, + // fontSize: 24.0, + // ), + // ), + // ), + // ), + // ), + // ), + // ); + // }).toList(), + // ) ], ); } diff --git a/lib/ui/mobile/goal_planner/goal_track_popup.dart b/lib/ui/mobile/goal_planner/goal_track_popup.dart index 771c95c..c6c25d0 100644 --- a/lib/ui/mobile/goal_planner/goal_track_popup.dart +++ b/lib/ui/mobile/goal_planner/goal_track_popup.dart @@ -9,9 +9,13 @@ import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/providers/grade_provider.dart'; import 'package:refilc_mobile_ui/common/average_display.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; +import 'package:refilc_plus/models/premium_scopes.dart'; +import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_input.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.dart'; +import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.i18n.dart'; +import 'package:refilc_plus/ui/mobile/goal_planner/route_option.dart'; class GoalTrackPopup extends StatefulWidget { const GoalTrackPopup({super.key, required this.subject}); @@ -45,6 +49,8 @@ class GoalTrackPopupState extends State { Plan? selectedRoute; List otherPlans = []; + bool plansPage = false; + @override void initState() { super.initState(); @@ -69,6 +75,75 @@ class GoalTrackPopupState extends State { return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!); } + PlanResult getResult() { + final currentAvg = GoalPlannerHelper.averageEvals(grades); + + recommended = null; + fastest = null; + otherPlans = []; + + if (currentAvg >= goalValue) return PlanResult.reached; + + final planner = GoalPlanner(goalValue, grades); + final plans = planner.solve(); + + plans.sort((a, b) => (a.avg - (2 * goalValue + 5) / 3) + .abs() + .compareTo(b.avg - (2 * goalValue + 5) / 3)); + + try { + final singleSolution = plans.every((e) => e.sigma == 0); + recommended = + plans.where((e) => singleSolution ? true : e.sigma > 0).first; + plans.removeWhere((e) => e == recommended); + } catch (_) {} + + plans.sort((a, b) => a.plan.length.compareTo(b.plan.length)); + + try { + fastest = plans.removeAt(0); + } catch (_) {} + + // print((recommended?.plan.length ?? 0).toString() + '-kuki'); + // print((fastest?.plan.length ?? 0).toString() + '--asd'); + + if ((((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0)) >= + 5) && + fastest != null) { + recommended = fastest; + } + + if (recommended == null) { + recommended = null; + fastest = null; + otherPlans = []; + selectedRoute = null; + return PlanResult.unsolvable; + } + + // print(recommended!.plan.length.toString() + '--------'); + + if (recommended!.plan.length > 20) { + recommended = null; + fastest = null; + otherPlans = []; + selectedRoute = null; + return PlanResult.unreachable; + } + + otherPlans = List.from(plans); + + // only save 2 items if not plus member + if (!Provider.of(context) + .hasScope(PremiumScopes.unlimitedGoalPlanner)) { + if (otherPlans.length > 2) { + otherPlans.removeRange(2, otherPlans.length - 1); + } + } + + return PlanResult.available; + } + void getGrades() { grades = getSubjectGrades(widget.subject).toList(); } @@ -77,12 +152,20 @@ class GoalTrackPopupState extends State { Widget build(BuildContext context) { gradeProvider = Provider.of(context); + getGrades(); + final currentAvg = GoalPlannerHelper.averageEvals(grades); + final result = getResult(); + List subjectGrades = getSubjectGrades(widget.subject); double avg = AverageHelper.averageEvals(subjectGrades); + double listLength = (otherPlans.length + + (recommended != null ? 1 : 0) + + (fastest != null && fastest != recommended ? 1 : 0)); + return Container( padding: const EdgeInsets.only(top: 24.0), child: SafeArea( @@ -113,23 +196,116 @@ class GoalTrackPopupState extends State { scale: 1.3, ), ], - ) + ), + const SizedBox( + height: 14.0, + ), + Text( + plansPage + ? 'goalplan_plans_title'.i18n + : 'goalplan_title'.i18n, + style: const TextStyle( + fontSize: 20.0, fontWeight: FontWeight.w700), + textAlign: TextAlign.center), + Text( + plansPage + ? 'goalplan_plans_subtitle'.i18n + : 'goalplan_subtitle'.i18n, + style: const TextStyle( + fontSize: 16.0, fontWeight: FontWeight.w500), + textAlign: TextAlign.center), ], ), const SizedBox(height: 24.0), - GoalInput( - value: goalValue, - currentAverage: currentAvg, - onChanged: (v) => setState(() { - selectedRoute = null; - goalValue = v; - }), - ), + if (!plansPage) + GoalInput( + value: goalValue, + currentAverage: currentAvg, + onChanged: (v) => setState(() { + selectedRoute = null; + goalValue = v; + }), + ), + if (plansPage && listLength > 2) + SizedBox( + height: (MediaQuery.of(context).size.height * 0.5), + child: SingleChildScrollView( + child: Column( + children: [ + if (recommended != null) + RouteOption( + plan: recommended!, + mark: RouteMark.recommended, + selected: selectedRoute == recommended!, + onSelected: () => setState(() { + selectedRoute = recommended; + }), + ), + if (fastest != null && fastest != recommended) + RouteOption( + plan: fastest!, + mark: RouteMark.fastest, + selected: selectedRoute == fastest!, + onSelected: () => setState(() { + selectedRoute = fastest; + }), + ), + ...otherPlans.map((e) => RouteOption( + plan: e, + selected: selectedRoute == e, + onSelected: () => setState(() { + selectedRoute = e; + }), + )), + if (result != PlanResult.available) + Text(result.name.i18n), + ], + ), + ), + ), + if (plansPage && listLength <= 2) + Column( + children: [ + if (recommended != null) + RouteOption( + plan: recommended!, + mark: RouteMark.recommended, + selected: selectedRoute == recommended!, + onSelected: () => setState(() { + selectedRoute = recommended; + }), + ), + if (fastest != null && fastest != recommended) + RouteOption( + plan: fastest!, + mark: RouteMark.fastest, + selected: selectedRoute == fastest!, + onSelected: () => setState(() { + selectedRoute = fastest; + }), + ), + ...otherPlans.map((e) => RouteOption( + plan: e, + selected: selectedRoute == e, + onSelected: () => setState(() { + selectedRoute = e; + }), + )), + if (result != PlanResult.available) Text(result.name.i18n), + ], + ), const SizedBox(height: 24.0), SizedBox( width: double.infinity, child: RawMaterialButton( onPressed: () async { + if (!plansPage) { + setState(() { + plansPage = true; + }); + return; + } + if (selectedRoute == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('${"pick_route".i18n}...'))); @@ -170,7 +346,7 @@ class GoalTrackPopupState extends State { shape: const StadiumBorder(), padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( - "track_it".i18n, + plansPage ? "track_it".i18n : "show_my_ways".i18n, style: const TextStyle( color: Colors.white, fontSize: 20.0, diff --git a/lib/ui/mobile/goal_planner/route_option.dart b/lib/ui/mobile/goal_planner/route_option.dart index 61e2357..8bf9be7 100644 --- a/lib/ui/mobile/goal_planner/route_option.dart +++ b/lib/ui/mobile/goal_planner/route_option.dart @@ -19,8 +19,9 @@ class RouteOption extends StatelessWidget { final bool selected; final void Function() onSelected; - Widget markLabel() { - const style = TextStyle(fontWeight: FontWeight.bold); + Widget markLabel({Color? colorOverride}) { + TextStyle style = + TextStyle(fontWeight: FontWeight.bold, color: colorOverride); switch (mark!) { case RouteMark.recommended: @@ -95,7 +96,7 @@ class RouteOption extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), side: selected - ? BorderSide(color: markColor(context), width: 4.0) + ? BorderSide(color: markColor(context), width: 1.5) : BorderSide.none, ), child: InkWell( @@ -109,21 +110,23 @@ class RouteOption extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (mark != null) ...[ - Chip( - label: markLabel(), - visualDensity: VisualDensity.compact, - backgroundColor: - selected ? markColor(context) : Colors.transparent, - labelPadding: const EdgeInsets.symmetric(horizontal: 8.0), - labelStyle: - TextStyle(color: selected ? Colors.white : null), - shape: StadiumBorder( - side: BorderSide( - color: markColor(context), - width: 3.0, - ), - ), - ), + // Chip( + // label: markLabel(), + // visualDensity: VisualDensity.compact, + // backgroundColor: + // selected ? markColor(context) : Colors.transparent, + // labelPadding: const EdgeInsets.symmetric(horizontal: 8.0), + // labelStyle: + // TextStyle(color: selected ? Colors.white : null), + // shape: StadiumBorder( + // side: BorderSide( + // color: markColor(context), + // width: 3.0, + // ), + // ), + // ), + markLabel( + colorOverride: selected ? markColor(context) : null), const SizedBox(height: 6.0), ], Wrap(