import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/helpers/average_helper.dart'; import 'package:refilc/helpers/subject.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/group_average.dart'; 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/round_border_icon.dart'; import 'package:refilc_mobile_ui/pages/grades/calculator/grade_calculator_provider.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.i18n.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_track_popup.dart'; import 'package:refilc_plus/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 GradeSubject subject; const GoalPlannerScreen({super.key, required this.subject}); @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(GradeSubject 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!); } Future> fetchGoalAverages() async { return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!); } // haha bees lol Future> fetchGoalBees() async { 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); 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(); } @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( top: 5.0, bottom: 220.0, right: 15.0, left: 2.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(), BackButton( color: Colors.red, onPressed: () => GoalTrackPopup.show(context, subject: widget.subject), ), 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), Padding( padding: const EdgeInsets.only(left: 22.0, right: 22.0), child: Column( crossAxisAlignment: CrossAxisAlignment.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: 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.i18n), ], ), ), ], ), ), 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.surface, 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(); final goalAvgs = await fetchGoalAverages(); final goalBeforeGrades = await fetchGoalBees(); final goalPinDates = await fetchGoalPinDates(); goalPlans[widget.subject.id] = selectedRoute!.dbString; goalAvgs[widget.subject.id] = goalValue.toStringAsFixed(2); goalBeforeGrades[widget.subject.id] = avg.toStringAsFixed(2); goalPinDates[widget.subject.id] = DateTime.now().toIso8601String(); // goalPlans[widget.subject.id] = '1,2,3,4,5,'; // goalAvgs[widget.subject.id] = '3.69'; // goalBeforeGrades[widget.subject.id] = '3.69'; // goalPinDates[widget.subject.id] = // DateTime.now().toIso8601String(); await dbProvider.userStore.storeSubjectGoalPlans( goalPlans, userId: user.id!); await dbProvider.userStore.storeSubjectGoalAverages( goalAvgs, userId: user.id!); await dbProvider.userStore.storeSubjectGoalBefores( goalBeforeGrades, userId: user.id!); await dbProvider.userStore.storeSubjectGoalPinDates( goalPinDates, userId: user.id!); // ignore: use_build_context_synchronously Navigator.of(context).pop(); }, fillColor: Theme.of(context).colorScheme.secondary, 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, ), ), ), ) ], ), ), ), ), ), ), ); } }