diff --git a/filcnaplo/lib/database/init.dart b/filcnaplo/lib/database/init.dart index 2cede66..8c01136 100644 --- a/filcnaplo/lib/database/init.dart +++ b/filcnaplo/lib/database/init.dart @@ -46,6 +46,8 @@ const userDataDB = DatabaseStruct("user_data", { "renamed_teachers": String, // "subject_lesson_count": String, // non kreta data "last_seen_grade": int, + // goal plans // non kreta data + "goal_plans": String, }); Future createTable(Database db, DatabaseStruct struct) => @@ -97,6 +99,8 @@ Future initDB(DatabaseProvider database) async { "renamed_teachers": "{}", // "subject_lesson_count": "{}", // non kreta data "last_seen_grade": 0, + // goal plans // non kreta data + "goal_plans": "{}", }); } catch (error) { print("ERROR: migrateDB: $error"); diff --git a/filcnaplo/lib/database/query.dart b/filcnaplo/lib/database/query.dart index fb892f6..4706615 100644 --- a/filcnaplo/lib/database/query.dart +++ b/filcnaplo/lib/database/query.dart @@ -213,4 +213,15 @@ class UserDatabaseQuery { return (jsonDecode(renamedTeachersJson) as Map) .map((key, value) => MapEntry(key.toString(), value.toString())); } + + Future> subjectGoalPlans({required String userId}) async { + List userData = + await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return {}; + String? goalPlansJson = + userData.elementAt(0)["goal_plans"] as String?; + if (goalPlansJson == null) return {}; + return (jsonDecode(goalPlansJson) as Map) + .map((key, value) => MapEntry(key.toString(), value.toString())); + } } diff --git a/filcnaplo/lib/database/store.dart b/filcnaplo/lib/database/store.dart index 86330b4..1319286 100644 --- a/filcnaplo/lib/database/store.dart +++ b/filcnaplo/lib/database/store.dart @@ -140,4 +140,11 @@ class UserDatabaseStore { await db.update("user_data", {"renamed_teachers": renamedTeachersJson}, where: "id = ?", whereArgs: [userId]); } + + Future storeSubjectGoalPlans(Map plans, + {required String userId}) async { + String goalPlansJson = jsonEncode(plans); + await db.update("user_data", {"goal_plans": goalPlansJson}, + where: "id = ?", whereArgs: [userId]); + } } diff --git a/filcnaplo_mobile_ui/lib/common/round_border_icon.dart b/filcnaplo_mobile_ui/lib/common/round_border_icon.dart index b24b5b1..f8c9220 100644 --- a/filcnaplo_mobile_ui/lib/common/round_border_icon.dart +++ b/filcnaplo_mobile_ui/lib/common/round_border_icon.dart @@ -8,7 +8,7 @@ class RoundBorderIcon extends StatelessWidget { const RoundBorderIcon( {Key? key, this.color = Colors.black, - this.width = 16.0, + this.width = 1.5, required this.icon}) : super(key: key); @@ -20,7 +20,7 @@ class RoundBorderIcon extends StatelessWidget { borderRadius: BorderRadius.circular(50.0), ), child: Padding( - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(5.0), child: icon, ), ); diff --git a/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart b/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart index 5b87c8e..29217c8 100755 --- a/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart +++ b/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart @@ -1,6 +1,8 @@ import 'dart:math'; import 'package:animations/animations.dart'; +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; @@ -21,7 +23,7 @@ import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_pro import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart'; -import 'package:filcnaplo_premium/ui/mobile/goal_planner/test.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner_screen.dart'; import 'package:filcnaplo_premium/models/premium_scopes.dart'; import 'package:filcnaplo_premium/providers/premium_provider.dart'; import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart'; @@ -62,12 +64,16 @@ class _GradeSubjectViewState extends State { late GradeProvider gradeProvider; late GradeCalculatorProvider calculatorProvider; late SettingsProvider settingsProvider; + late DatabaseProvider dbProvider; + late UserProvider user; late double average; late Widget gradeGraph; bool gradeCalcMode = false; + String plan = ''; + List getSubjectGrades(Subject subject) => !gradeCalcMode ? gradeProvider.grades.where((e) => e.subject == subject).toList() : calculatorProvider.grades.where((e) => e.subject == subject).toList(); @@ -151,6 +157,20 @@ class _GradeSubjectViewState extends State { gradeTiles = List.castFrom(tiles); } + @override + void initState() { + super.initState(); + user = Provider.of(context, listen: false); + dbProvider = Provider.of(context, listen: false); + } + + void fetchGoalPlans() async { + plan = (await dbProvider.userQuery + .subjectGoalPlans(userId: user.id!))[widget.subject.id] ?? + ''; + setState(() {}); + } + @override Widget build(BuildContext context) { gradeProvider = Provider.of(context); @@ -196,6 +216,8 @@ class _GradeSubjectViewState extends State { buildTiles(ghostGrades); } + fetchGoalPlans(); + return Scaffold( key: _scaffoldKey, floatingActionButtonLocation: ExpandableFab.location, @@ -213,6 +235,7 @@ class _GradeSubjectViewState extends State { ), children: [ FloatingActionButton.small( + heroTag: "btn_ghost_grades", child: const Icon(FeatherIcons.plus), backgroundColor: Theme.of(context).colorScheme.secondary, onPressed: () { @@ -220,6 +243,7 @@ class _GradeSubjectViewState extends State { }, ), FloatingActionButton.small( + heroTag: "btn_goal_planner", child: const Icon(FeatherIcons.flag, size: 20.0), backgroundColor: Theme.of(context).colorScheme.secondary, onPressed: () { @@ -235,7 +259,7 @@ class _GradeSubjectViewState extends State { Navigator.of(context).push(CupertinoPageRoute( builder: (context) => - GoalPlannerTest(subject: widget.subject))); + GoalPlannerScreen(subject: widget.subject))); }, ), ], @@ -261,6 +285,35 @@ class _GradeSubjectViewState extends State { const SizedBox(width: 6.0), if (average != 0) Center(child: AverageDisplay(average: average)), + const SizedBox(width: 6.0), + if (plan != '') + Center( + child: GestureDetector( + onTap: () { + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => + GoalPlannerScreen(subject: widget.subject))); + }, + child: Container( + width: 54.0, + padding: const EdgeInsets.symmetric( + horizontal: 5.0, vertical: 8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45.0), + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(.15), + ), + child: Icon( + FeatherIcons.flag, + size: 17.0, + weight: 2.5, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), const SizedBox(width: 12.0), ], icon: SubjectIcon.resolveVariant( diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner.dart index 52b4a93..22878a9 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner.dart @@ -138,6 +138,14 @@ class Plan { Plan(this.plan); + String get dbString { + var finalString = ''; + for (var i in plan) { + finalString += "$i,"; + } + return finalString; + } + @override bool operator ==(other) => other is Plan && listEquals(plan, other.plan); diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart similarity index 74% rename from filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart rename to filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart index 401f6a5..3db2e2d 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.dart @@ -1,360 +1,380 @@ -import 'package:filcnaplo/helpers/average_helper.dart'; -import 'package:filcnaplo/helpers/subject.dart'; -import 'package:filcnaplo/models/settings.dart'; -import 'package:filcnaplo_kreta_api/models/grade.dart'; -import 'package:filcnaplo_kreta_api/models/group_average.dart'; -import 'package:filcnaplo_kreta_api/models/subject.dart'; -import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; -import 'package:filcnaplo_mobile_ui/common/average_display.dart'; -import 'package:filcnaplo_mobile_ui/common/round_border_icon.dart'; -import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart'; -import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_input.dart'; -import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner.dart'; -import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner_screen.i18n.dart'; -import 'package:filcnaplo_premium/ui/mobile/goal_planner/route_option.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -enum PlanResult { - available, // There are possible solutions - unreachable, // The solutions are too hard don't even try - unsolvable, // There are no solutions - reached, // Goal already reached -} - -class GoalPlannerTest extends StatefulWidget { - final Subject subject; - - const GoalPlannerTest({Key? key, required this.subject}) : super(key: key); - - @override - State createState() => _GoalPlannerTestState(); -} - -class _GoalPlannerTestState extends State { - late GradeProvider gradeProvider; - late GradeCalculatorProvider calculatorProvider; - late SettingsProvider settingsProvider; - - bool gradeCalcMode = false; - - List getSubjectGrades(Subject subject) => !gradeCalcMode - ? gradeProvider.grades.where((e) => e.subject == subject).toList() - : calculatorProvider.grades.where((e) => e.subject == subject).toList(); - - double goalValue = 4.0; - List grades = []; - - Plan? recommended; - Plan? fastest; - Plan? selectedRoute; - List otherPlans = []; - - 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 (_) {} - - if ((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0) >= 3) { - recommended = fastest; - } - - if (recommended == null) { - recommended = null; - fastest = null; - otherPlans = []; - selectedRoute = null; - return PlanResult.unsolvable; - } - - if (recommended!.plan.length > 10) { - recommended = null; - fastest = null; - otherPlans = []; - selectedRoute = null; - return PlanResult.unreachable; - } - - otherPlans = List.from(plans); - - return PlanResult.available; - } - - void getGrades() { - grades = getSubjectGrades(widget.subject).toList(); - } - - @override - Widget build(BuildContext context) { - gradeProvider = Provider.of(context); - calculatorProvider = Provider.of(context); - settingsProvider = Provider.of(context); - - getGrades(); - - final currentAvg = GoalPlannerHelper.averageEvals(grades); - - final result = getResult(); - - List subjectGrades = getSubjectGrades(widget.subject); - - double avg = AverageHelper.averageEvals(subjectGrades); - - var nullavg = GroupAverage(average: 0.0, subject: widget.subject, uid: "0"); - double groupAverage = gradeProvider.groupAverages - .firstWhere((e) => e.subject == widget.subject, orElse: () => nullavg) - .average; - - return Scaffold( - body: SafeArea( - child: ListView( - padding: const EdgeInsets.only( - left: 22.0, right: 22.0, top: 5.0, bottom: 220.0), - children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // const BackButton(), - // Padding( - // padding: const EdgeInsets.only(right: 15.0), - // child: Row( - // children: [ - // Text( - // 'goal_planner_title'.i18n, - // style: const TextStyle( - // fontWeight: FontWeight.w500, fontSize: 18.0), - // ), - // const SizedBox( - // width: 5, - // ), - // const BetaChip(), - // ], - // ), - // ), - // ], - // ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - children: [ - const BackButton(), - RoundBorderIcon( - icon: Icon( - SubjectIcon.resolveVariant( - context: context, - subject: widget.subject, - ), - size: 10, - ), - ), - Text( - (widget.subject.isRenamed - ? widget.subject.renamedTo - : widget.subject.name) ?? - 'goal_planner_title'.i18n, - style: const TextStyle( - fontSize: 17.0, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - Row( - children: [ - if (groupAverage != 0) - AverageDisplay(average: groupAverage, border: true), - const SizedBox(width: 6.0), - AverageDisplay(average: avg) - ], - ), - ], - ), - const SizedBox(height: 12.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "set_a_goal".i18n, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.0, - ), - ), - const SizedBox(height: 4.0), - Text( - goalValue.toString(), - style: TextStyle( - fontWeight: FontWeight.w900, - fontSize: 42.0, - color: gradeColor(goalValue.round(), settingsProvider), - ), - ), - ], - ), - // Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Text( - // "select_subject".i18n, - // style: const TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 20.0, - // ), - // ), - // const SizedBox(height: 4.0), - // Column( - // children: [ - // Icon( - // SubjectIcon.resolveVariant( - // context: context, - // subject: widget.subject, - // ), - // size: 48.0, - // ), - // Text( - // (widget.subject.isRenamed - // ? widget.subject.renamedTo - // : widget.subject.name) ?? - // '', - // style: const TextStyle( - // fontSize: 17.0, - // fontWeight: FontWeight.w500, - // ), - // ) - // ], - // ) - // ], - // ) - ], - ), - const SizedBox(height: 24.0), - Text( - "pick_route".i18n, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.0, - ), - ), - const SizedBox(height: 12.0), - 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), - ], - ), - ), - bottomSheet: MediaQuery.removePadding( - context: context, - removeBottom: false, - removeTop: true, - child: Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: Container( - padding: const EdgeInsets.only(top: 24.0), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: - const BorderRadius.vertical(top: Radius.circular(24.0)), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(.1), - blurRadius: 8.0, - ) - ]), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - GoalInput( - value: goalValue, - currentAverage: currentAvg, - onChanged: (v) => setState(() { - selectedRoute = null; - goalValue = v; - }), - ), - const SizedBox(height: 24.0), - SizedBox( - width: double.infinity, - child: RawMaterialButton( - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Hamarosan..."))); - }, - fillColor: Theme.of(context).colorScheme.primary, - shape: const StadiumBorder(), - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - "track_it".i18n, - style: const TextStyle( - color: Colors.white, - fontSize: 20.0, - fontWeight: FontWeight.w600, - ), - ), - ), - ) - ], - ), - ), - ), - ), - ), - ), - ); - } -} +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/api/providers/user_provider.dart'; +import 'package:filcnaplo/helpers/average_helper.dart'; +import 'package:filcnaplo/helpers/subject.dart'; +import 'package:filcnaplo/models/settings.dart'; +import 'package:filcnaplo_kreta_api/models/grade.dart'; +import 'package:filcnaplo_kreta_api/models/group_average.dart'; +import 'package:filcnaplo_kreta_api/models/subject.dart'; +import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; +import 'package:filcnaplo_mobile_ui/common/average_display.dart'; +import 'package:filcnaplo_mobile_ui/common/round_border_icon.dart'; +import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_input.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner_screen.i18n.dart'; +import 'package:filcnaplo_premium/ui/mobile/goal_planner/route_option.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +enum PlanResult { + available, // There are possible solutions + unreachable, // The solutions are too hard don't even try + unsolvable, // There are no solutions + reached, // Goal already reached +} + +class GoalPlannerScreen extends StatefulWidget { + final Subject subject; + + const GoalPlannerScreen({Key? key, required this.subject}) : super(key: key); + + @override + State createState() => _GoalPlannerScreenState(); +} + +class _GoalPlannerScreenState extends State { + late GradeProvider gradeProvider; + late GradeCalculatorProvider calculatorProvider; + late SettingsProvider settingsProvider; + late DatabaseProvider dbProvider; + late UserProvider user; + + bool gradeCalcMode = false; + + List getSubjectGrades(Subject subject) => !gradeCalcMode + ? gradeProvider.grades.where((e) => e.subject == subject).toList() + : calculatorProvider.grades.where((e) => e.subject == subject).toList(); + + double goalValue = 4.0; + List grades = []; + + Plan? recommended; + Plan? fastest; + Plan? selectedRoute; + List otherPlans = []; + + @override + void initState() { + super.initState(); + user = Provider.of(context, listen: false); + dbProvider = Provider.of(context, listen: false); + } + + Future> fetchGoalPlans() async { + return await dbProvider.userQuery.subjectGoalPlans(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 (_) {} + + if ((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0) >= 3) { + recommended = fastest; + } + + if (recommended == null) { + recommended = null; + fastest = null; + otherPlans = []; + selectedRoute = null; + return PlanResult.unsolvable; + } + + if (recommended!.plan.length > 10) { + recommended = null; + fastest = null; + otherPlans = []; + selectedRoute = null; + return PlanResult.unreachable; + } + + otherPlans = List.from(plans); + + return PlanResult.available; + } + + void getGrades() { + grades = getSubjectGrades(widget.subject).toList(); + } + + @override + Widget build(BuildContext context) { + gradeProvider = Provider.of(context); + calculatorProvider = Provider.of(context); + settingsProvider = Provider.of(context); + + getGrades(); + + final currentAvg = GoalPlannerHelper.averageEvals(grades); + + final result = getResult(); + + List subjectGrades = getSubjectGrades(widget.subject); + + double avg = AverageHelper.averageEvals(subjectGrades); + + var nullavg = GroupAverage(average: 0.0, subject: widget.subject, uid: "0"); + double groupAverage = gradeProvider.groupAverages + .firstWhere((e) => e.subject == widget.subject, orElse: () => nullavg) + .average; + + return Scaffold( + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only( + left: 22.0, right: 22.0, top: 5.0, bottom: 220.0), + children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // const BackButton(), + // Padding( + // padding: const EdgeInsets.only(right: 15.0), + // child: Row( + // children: [ + // Text( + // 'goal_planner_title'.i18n, + // style: const TextStyle( + // fontWeight: FontWeight.w500, fontSize: 18.0), + // ), + // const SizedBox( + // width: 5, + // ), + // const BetaChip(), + // ], + // ), + // ), + // ], + // ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + const BackButton(), + RoundBorderIcon( + icon: Icon( + SubjectIcon.resolveVariant( + context: context, + subject: widget.subject, + ), + size: 18, + weight: 1.5, + ), + ), + const SizedBox( + width: 5.0, + ), + Text( + (widget.subject.isRenamed + ? widget.subject.renamedTo + : widget.subject.name) ?? + 'goal_planner_title'.i18n, + style: const TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + Row( + children: [ + if (groupAverage != 0) + AverageDisplay(average: groupAverage, border: true), + const SizedBox(width: 6.0), + AverageDisplay(average: avg) + ], + ), + ], + ), + const SizedBox(height: 12.0), + Text( + "set_a_goal".i18n, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), + const SizedBox(height: 4.0), + Text( + goalValue.toString(), + style: TextStyle( + fontWeight: FontWeight.w900, + fontSize: 48.0, + color: gradeColor(goalValue.round(), settingsProvider), + ), + ), + // Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // "select_subject".i18n, + // style: const TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 20.0, + // ), + // ), + // const SizedBox(height: 4.0), + // Column( + // children: [ + // Icon( + // SubjectIcon.resolveVariant( + // context: context, + // subject: widget.subject, + // ), + // size: 48.0, + // ), + // Text( + // (widget.subject.isRenamed + // ? widget.subject.renamedTo + // : widget.subject.name) ?? + // '', + // style: const TextStyle( + // fontSize: 17.0, + // fontWeight: FontWeight.w500, + // ), + // ) + // ], + // ) + // ], + // ) + const SizedBox(height: 24.0), + Text( + "pick_route".i18n, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), + const SizedBox(height: 12.0), + 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), + ], + ), + ), + bottomSheet: MediaQuery.removePadding( + context: context, + removeBottom: false, + removeTop: true, + child: Container( + color: Theme.of(context).scaffoldBackgroundColor, + child: Container( + padding: const EdgeInsets.only(top: 24.0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: + const BorderRadius.vertical(top: Radius.circular(24.0)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(.1), + blurRadius: 8.0, + ) + ]), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GoalInput( + value: goalValue, + currentAverage: currentAvg, + onChanged: (v) => setState(() { + selectedRoute = null; + goalValue = v; + }), + ), + const SizedBox(height: 24.0), + SizedBox( + width: double.infinity, + child: RawMaterialButton( + onPressed: () async { + if (selectedRoute == null) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('${"pick_route".i18n}...'))); + } + + final goalPlans = await fetchGoalPlans(); + goalPlans[widget.subject.id] = + selectedRoute!.dbString; + + await dbProvider.userStore.storeSubjectGoalPlans( + goalPlans, + userId: user.id!); + + Navigator.of(context).pop(); + }, + fillColor: Theme.of(context).colorScheme.primary, + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + "track_it".i18n, + style: const TextStyle( + color: Colors.white, + fontSize: 20.0, + fontWeight: FontWeight.w600, + ), + ), + ), + ) + ], + ), + ), + ), + ), + ), + ), + ); + } +}