diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart index 4cf8acf..d706922 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart @@ -74,6 +74,10 @@ class _GoalPlannerScreenState extends State { return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!); } + Future> fetchGoalPinDates() async { + return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!); + } + PlanResult getResult() { final currentAvg = GoalPlannerHelper.averageEvals(grades); @@ -369,6 +373,7 @@ class _GoalPlannerScreenState extends State { final goalPlans = await fetchGoalPlans(); final goalAvgs = await fetchGoalAverages(); final goalBeforeGrades = await fetchGoalBees(); + final goalPinDates = await fetchGoalPinDates(); goalPlans[widget.subject.id] = selectedRoute!.dbString; @@ -376,6 +381,8 @@ class _GoalPlannerScreenState extends State { goalValue.toStringAsFixed(1); goalBeforeGrades[widget.subject.id] = avg.toStringAsFixed(1); + goalPinDates[widget.subject.id] = + DateTime.now().toIso8601String(); await dbProvider.userStore.storeSubjectGoalPlans( goalPlans, @@ -386,6 +393,9 @@ class _GoalPlannerScreenState extends State { await dbProvider.userStore.storeSubjectGoalBefores( goalBeforeGrades, userId: user.id!); + await dbProvider.userStore.storeSubjectGoalPinDates( + goalPinDates, + userId: user.id!); Navigator.of(context).pop(); }, diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.dart index d797fe9..b843c1e 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.dart @@ -9,7 +9,9 @@ import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo_mobile_ui/common/panel/panel.dart'; import 'package:filcnaplo_mobile_ui/common/progress_bar.dart'; import 'package:filcnaplo_mobile_ui/common/round_border_icon.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner.dart'; import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_state_screen.i18n.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/route_option.dart'; import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; @@ -36,6 +38,8 @@ class _GoalStateScreenState extends State { double beforeAvg = 0.0; double avgDifference = 0; + Plan? plan; + late Widget gradeGraph; DateTime goalPinDate = DateTime.now(); @@ -57,6 +61,18 @@ class _GoalStateScreenState extends State { setState(() {}); } + void fetchGoalPlan() async { + var planRes = await db.userQuery.subjectGoalPlans(userId: user.id!); + List prePlan = planRes[widget.subject.id]!.split(','); + prePlan.removeLast(); + + plan = Plan( + prePlan.map((e) => int.parse(e)).toList(), + ); + + setState(() {}); + } + List getSubjectGrades(Subject subject) => gradeProvider.grades .where((e) => (e.subject == subject && e.date.isAfter(goalPinDate))) .toList(); @@ -69,6 +85,7 @@ class _GoalStateScreenState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { fetchGoalAverages(); + fetchGoalPlan(); }); } @@ -137,206 +154,254 @@ class _GoalStateScreenState extends State { ); return Scaffold( - body: Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/images/subject_covers/math_light.png'), - fit: BoxFit.fitWidth, - alignment: Alignment.topCenter, - ), - ), - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context).scaffoldBackgroundColor.withOpacity(0.2), - Theme.of(context).scaffoldBackgroundColor, - ], - stops: const [ - 0.1, - 0.22, - ], + body: ListView( + padding: EdgeInsets.zero, + children: [ + Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: + AssetImage('assets/images/subject_covers/math_light.png'), + fit: BoxFit.fitWidth, + alignment: Alignment.topCenter, + ), ), - ), - child: Padding( - padding: const EdgeInsets.only(top: 10.0, left: 2.0, right: 2.0), - child: ListView( - children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - BackButton(), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context).scaffoldBackgroundColor.withOpacity(0.2), + Theme.of(context).scaffoldBackgroundColor, + ], + stops: const [ + 0.1, + 0.22, ], ), - const SizedBox(height: 22.0), - Column( + ), + child: Padding( + padding: const EdgeInsets.only( + top: 60.0, + left: 2.0, + right: 2.0, + ), + child: Column( children: [ - RoundBorderIcon( - icon: Icon( - SubjectIcon.resolveVariant( - context: context, - subject: widget.subject, - ), - size: 26.0, - weight: 2.5, - ), - padding: 8.0, - width: 2.5, - ), - const SizedBox( - height: 10.0, - ), - Text( - (widget.subject.isRenamed - ? widget.subject.renamedTo - : widget.subject.name) ?? - 'goal_planner_title'.i18n, - style: const TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.w700, - ), - ), - Text( - 'almost_there'.i18n, - style: const TextStyle( - fontSize: 22.0, - fontWeight: FontWeight.w400, - height: 1.0, - ), - ), - ], - ), - const SizedBox(height: 28.0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - 'started_with'.i18n, - style: const TextStyle( - fontWeight: FontWeight.w700, - fontSize: 20.0, - ), - ), - ], - ), - Row( - children: [ - Text( - 'current'.i18n, - style: const TextStyle( - fontWeight: FontWeight.w700, - fontSize: 20.0, - ), - ), - ], - ), - ], - ), - ), - const SizedBox(height: 10.0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Panel( - padding: const EdgeInsets.all(18.0), - child: Column( + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'your_goal'.i18n, - style: const TextStyle( - fontSize: 23.0, - fontWeight: FontWeight.w700, - ), + BackButton(), + ], + ), + const SizedBox(height: 22.0), + Column( + children: [ + RoundBorderIcon( + icon: Icon( + SubjectIcon.resolveVariant( + context: context, + subject: widget.subject, ), - RawMaterialButton( - onPressed: () async {}, - fillColor: Colors.black, - shape: const StadiumBorder(), - padding: - const EdgeInsets.symmetric(horizontal: 18.0), - child: Text( - "change_it".i18n, - style: const TextStyle( - height: 1.0, - color: Colors.white, - fontSize: 14.0, - fontWeight: FontWeight.w600, - ), - ), - ), - ], + size: 26.0, + weight: 2.5, + ), + padding: 8.0, + width: 2.5, ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - goalAvg.toString(), - style: const TextStyle( - height: 1.1, - fontSize: 42.0, - fontWeight: FontWeight.w800, - ), - ), - const SizedBox(width: 10.0), - Center( - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 5.0, - horizontal: 8.0, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(45.0), - color: Colors.greenAccent.shade700 - .withOpacity(.15), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - FeatherIcons.chevronUp, - color: Colors.greenAccent.shade700, - size: 18.0, - ), - const SizedBox(width: 5.0), - Text( - avgDifference.toStringAsFixed(2) + '%', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.greenAccent.shade700, - fontSize: 22.0, - height: 0.8, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ), - ], + const SizedBox( + height: 10.0, + ), + Text( + (widget.subject.isRenamed + ? widget.subject.renamedTo + : widget.subject.name) ?? + 'goal_planner_title'.i18n, + style: const TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.w700, + ), + ), + Text( + 'almost_there'.i18n, + style: const TextStyle( + fontSize: 22.0, + fontWeight: FontWeight.w400, + height: 1.0, + ), ), ], ), - ), + const SizedBox(height: 28.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'started_with'.i18n, + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20.0, + ), + ), + ], + ), + Row( + children: [ + Text( + 'current'.i18n, + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20.0, + ), + ), + ], + ), + ], + ), + ), + const SizedBox(height: 10.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Panel( + padding: const EdgeInsets.all(18.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'your_goal'.i18n, + style: const TextStyle( + fontSize: 23.0, + fontWeight: FontWeight.w700, + ), + ), + RawMaterialButton( + onPressed: () async {}, + fillColor: Colors.black, + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric( + horizontal: 18.0), + child: Text( + "change_it".i18n, + style: const TextStyle( + height: 1.0, + color: Colors.white, + fontSize: 14.0, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + goalAvg.toString(), + style: const TextStyle( + height: 1.1, + fontSize: 42.0, + fontWeight: FontWeight.w800, + ), + ), + const SizedBox(width: 10.0), + Center( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 5.0, + horizontal: 8.0, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45.0), + color: Colors.greenAccent.shade700 + .withOpacity(.15), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Icon( + FeatherIcons.chevronUp, + color: Colors.greenAccent.shade700, + size: 18.0, + ), + const SizedBox(width: 5.0), + Text( + avgDifference.toStringAsFixed(2) + + '%', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.greenAccent.shade700, + fontSize: 22.0, + height: 0.8, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 5.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: gradeGraph, + ), + const SizedBox(height: 5.0), + Padding( + padding: const EdgeInsets.only( + left: 12.0, + right: 12.0, + top: 5.0, + bottom: 8.0, + ), + child: Panel( + padding: const EdgeInsets.all(18.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'you_need'.i18n, + style: const TextStyle( + fontSize: 23.0, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + const SizedBox(height: 8.0), + plan != null + ? RouteOptionRow( + plan: plan!, + ) + : const Text(''), + ], + ), + ), + ), + ], ), - const SizedBox(height: 5.0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: gradeGraph, - ), - ], + ), ), ), - ), + ], ), ); } diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.i18n.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.i18n.dart index 90fabd5..6abe77c 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.i18n.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_state_screen.i18n.dart @@ -6,29 +6,38 @@ extension Localization on String { "en_en": { "goal_planner_title": "Goal Planning", "almost_there": "Almost there! Keep going!", - "select_subject": "Subject", - "pick_route": "Pick a Route", - "track_it": "Track it!", - "recommended": "Recommended", - "fastest": "Fastest", + "started_with": "Started with:", + "current": "Current:", + "your_goal": "Your goal:", + "change_it": "Change it", + "look_at_graph": "Look at this graph!", + "thats_progress": + "Now that's what I call progress! Push a little more, you're almost there..", + "you_need": "You need:", }, "hu_hu": { "goal_planner_title": "Cél követés", - "set_a_goal": "Kitűzött cél", - "select_subject": "Tantárgy", - "pick_route": "Válassz egy utat", - "track_it": "Követés!", - "recommended": "Ajánlott", - "fastest": "Leggyorsabb", + "almost_there": "Majdnem megvan! Így tovább!", + "started_with": "Így kezdődött:", + "current": "Jelenlegi:", + "your_goal": "Célod:", + "change_it": "Megváltoztatás", + "look_at_graph": "Nézd meg ezt a grafikont!", + "thats_progress": + "Ezt nevezem haladásnak! Hajts még egy kicsit, már majdnem kész..", + "you_need": "Szükséges:", }, "de_de": { "goal_planner_title": "Zielplanung", - "set_a_goal": "Dein Ziel", - "select_subject": "Thema", - "pick_route": "Wähle einen Weg", - "track_it": "Verfolge es!", - "recommended": "Empfohlen", - "fastest": "Am schnellsten", + "almost_there": "Fast dort! Weitermachen!", + "started_with": "Begann mit:", + "current": "Aktuell:", + "your_goal": "Dein Ziel:", + "change_it": "Ändern Sie es", + "look_at_graph": "Schauen Sie sich diese Grafik an!", + "thats_progress": + "Das nenne ich Fortschritt! Drücken Sie noch ein wenig, Sie haben es fast geschafft..", + "you_need": "Du brauchst:", }, }; diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart index 3d02d26..dfc4b8b 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart @@ -141,3 +141,62 @@ class RouteOption extends StatelessWidget { ); } } + +class RouteOptionRow extends StatelessWidget { + const RouteOptionRow({ + Key? key, + required this.plan, + this.mark, + }) : super(key: key); + + final Plan plan; + final RouteMark? mark; + + @override + Widget build(BuildContext context) { + List gradeWidgets = []; + + for (int i = 5; i > 1; i--) { + final count = plan.plan.where((e) => e == i).length; + + if (count > 4) { + gradeWidgets.add(Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${count}x", + style: TextStyle( + fontSize: 22.0, + fontWeight: FontWeight.w500, + color: AppColors.of(context).text.withOpacity(.7), + ), + ), + const SizedBox(width: 4.0), + GradeDisplay(grade: i), + ], + )); + } else { + gradeWidgets + .addAll(List.generate(count, (_) => GradeDisplay(grade: i))); + } + + if (count > 0) { + gradeWidgets.add(SizedBox( + height: 36.0, + width: 32.0, + child: Center( + child: Icon(Icons.add, + color: AppColors.of(context).text.withOpacity(.5))), + )); + } + } + + gradeWidgets.removeLast(); + + return Wrap( + spacing: 4.0, + runSpacing: 8.0, + children: gradeWidgets, + ); + } +}