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 a638637..5b87c8e 100755 --- a/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart +++ b/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart @@ -21,6 +21,10 @@ 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/models/premium_scopes.dart'; +import 'package:filcnaplo_premium/providers/premium_provider.dart'; +import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; @@ -215,25 +219,25 @@ class _GradeSubjectViewState extends State { gradeCalc(context); }, ), - // FloatingActionButton.small( - // child: const Icon(FeatherIcons.flag, size: 20.0), - // backgroundColor: Theme.of(context).colorScheme.secondary, - // onPressed: () { - // if (!Provider.of(context, listen: false) - // .hasScope(PremiumScopes.goalPlanner)) { - // PremiumLockedFeatureUpsell.show( - // context: context, feature: PremiumFeature.goalplanner); - // return; - // } + FloatingActionButton.small( + child: const Icon(FeatherIcons.flag, size: 20.0), + backgroundColor: Theme.of(context).colorScheme.secondary, + onPressed: () { + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.goalPlanner)) { + PremiumLockedFeatureUpsell.show( + context: context, feature: PremiumFeature.goalplanner); + return; + } - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar(content: Text("Hamarosan..."))); + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text("Hamarosan..."))); - // Navigator.of(context).push(CupertinoPageRoute( - // builder: (context) => PremiumGoalplannerNewGoalScreen( - // subject: widget.subject))); - // }, - // ), + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => + GoalPlannerTest(subject: widget.subject))); + }, + ), ], ), ), @@ -263,7 +267,8 @@ class _GradeSubjectViewState extends State { subject: widget.subject, context: context), scrollController: _scrollController, title: widget.subject.renamedTo ?? widget.subject.name.capital(), - italic: settingsProvider.renamedSubjectsItalics && widget.subject.isRenamed, + italic: settingsProvider.renamedSubjectsItalics && + widget.subject.isRenamed, child: SubjectGradesContainer( child: CupertinoScrollbar( child: ListView.builder( diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_input.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_input.dart index 678d270..6fdfe54 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_input.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_input.dart @@ -1,8 +1,15 @@ +import 'package:filcnaplo/models/settings.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; class GoalInput extends StatelessWidget { - const GoalInput({Key? key, required this.currentAverage, required this.value, required this.onChanged}) : super(key: key); + const GoalInput( + {Key? key, + required this.currentAverage, + required this.value, + required this.onChanged}) + : super(key: key); final double currentAverage; final double value; @@ -24,6 +31,8 @@ class GoalInput extends StatelessWidget { @override Widget build(BuildContext context) { + SettingsProvider settings = Provider.of(context); + List presets = [2, 3, 4, 5]; presets = presets.where((e) => gradeToAvg(e) > currentAverage).toList(); @@ -44,7 +53,8 @@ class GoalInput extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(right: 20.0), child: CustomPaint( - painter: GoalSliderPainter(value: (value - 1) / 4), + painter: GoalSliderPainter( + value: (value - 1) / 4, settings: settings), ), ), ), @@ -61,8 +71,9 @@ class GoalInput extends StatelessWidget { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(99.0), - color: gradeColor(e).withOpacity(selected ? 1.0 : 0.2), - border: Border.all(color: gradeColor(e), width: 4), + color: + gradeColor(e, settings).withOpacity(selected ? 1.0 : 0.2), + border: Border.all(color: gradeColor(e, settings), width: 4), ), child: Material( type: MaterialType.transparency, @@ -70,11 +81,13 @@ class GoalInput extends StatelessWidget { borderRadius: BorderRadius.circular(99.0), onTap: () => setValue(gradeToAvg(e)), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 24.0), + padding: const EdgeInsets.symmetric( + vertical: 2.0, horizontal: 24.0), child: Text( e.toString(), style: TextStyle( - color: selected ? Colors.white : gradeColor(e), + color: + selected ? Colors.white : gradeColor(e, settings), fontWeight: FontWeight.bold, fontSize: 24.0, ), @@ -93,8 +106,9 @@ class GoalInput extends StatelessWidget { class GoalSliderPainter extends CustomPainter { final double value; + final SettingsProvider settings; - GoalSliderPainter({required this.value}); + GoalSliderPainter({required this.value, required this.settings}); @override void paint(Canvas canvas, Size size) { @@ -115,21 +129,24 @@ class GoalSliderPainter extends CustomPainter { const Radius.circular(99.0), ), Paint() - ..shader = const LinearGradient(colors: [ - Color(0xffFF3B30), - Color(0xffFF9F0A), - Color(0xffFFD60A), - Color(0xff34C759), - Color(0xff247665), + ..shader = LinearGradient(colors: [ + settings.gradeColors[0], + settings.gradeColors[1], + settings.gradeColors[2], + settings.gradeColors[3], + settings.gradeColors[4], ]).createShader(rect), ); canvas.drawOval( - Rect.fromCircle(center: Offset(size.width * value, size.height / 2), radius: radius - cpadding), + Rect.fromCircle( + center: Offset(size.width * value, size.height / 2), + radius: radius - cpadding), Paint()..color = Colors.white, ); for (int i = 1; i < 4; i++) { canvas.drawOval( - Rect.fromCircle(center: Offset(size.width / 4 * i, size.height / 2), radius: 4), + Rect.fromCircle( + center: Offset(size.width / 4 * i, size.height / 2), radius: 4), Paint()..color = Colors.white.withOpacity(.5), ); } @@ -145,12 +162,19 @@ double gradeToAvg(int grade) { return grade - 0.5; } -Color gradeColor(int grade) { +Color gradeColor(int grade, SettingsProvider settings) { + // return [ + // const Color(0xffFF3B30), + // const Color(0xffFF9F0A), + // const Color(0xffFFD60A), + // const Color(0xff34C759), + // const Color(0xff247665), + // ].elementAt(grade.clamp(1, 5) - 1); return [ - const Color(0xffFF3B30), - const Color(0xffFF9F0A), - const Color(0xffFFD60A), - const Color(0xff34C759), - const Color(0xff247665), + settings.gradeColors[0], + settings.gradeColors[1], + settings.gradeColors[2], + settings.gradeColors[3], + settings.gradeColors[4], ].elementAt(grade.clamp(1, 5) - 1); } diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.i18n.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.i18n.dart new file mode 100644 index 0000000..6855167 --- /dev/null +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/goal_planner_screen.i18n.dart @@ -0,0 +1,39 @@ +import 'package:i18n_extension/i18n_extension.dart'; + +extension Localization on String { + static final _t = Translations.byLocale("hu_hu") + + { + "en_en": { + "goal_planner_title": "Goal Planning", + "set_a_goal": "Your Goal", + "select_subject": "Subject", + "pick_route": "Pick a Route", + "track_it": "Track it!", + "recommended": "Recommended", + "fastest": "Fastest", + }, + "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", + }, + "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", + }, + }; + + String get i18n => localize(this, _t); + String fill(List params) => localizeFill(this, params); + String plural(int value) => localizePlural(value, this, _t); + String version(Object modifier) => localizeVersion(modifier, this, _t); +} diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/grade_display.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/grade_display.dart index 8ac288d..1a1142a 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/grade_display.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/grade_display.dart @@ -1,5 +1,7 @@ +import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_input.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class GradeDisplay extends StatelessWidget { const GradeDisplay({Key? key, required this.grade}) : super(key: key); @@ -8,12 +10,14 @@ class GradeDisplay extends StatelessWidget { @override Widget build(BuildContext context) { + SettingsProvider settings = Provider.of(context); + return Container( width: 36, height: 36, decoration: BoxDecoration( shape: BoxShape.circle, - color: gradeColor(grade).withOpacity(.3), + color: gradeColor(grade, settings).withOpacity(.3), ), child: Center( child: Text( @@ -21,7 +25,7 @@ class GradeDisplay extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 22.0, - color: gradeColor(grade), + color: gradeColor(grade, settings), ), ), ), 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 bc19b8c..8c7f9ca 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/route_option.dart @@ -1,11 +1,18 @@ 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/grade_display.dart'; import 'package:flutter/material.dart'; enum RouteMark { recommended, fastest } class RouteOption extends StatelessWidget { - const RouteOption({Key? key, required this.plan, this.mark, this.selected = false, required this.onSelected}) : super(key: key); + const RouteOption( + {Key? key, + required this.plan, + this.mark, + this.selected = false, + required this.onSelected}) + : super(key: key); final Plan plan; final RouteMark? mark; @@ -17,20 +24,20 @@ class RouteOption extends StatelessWidget { switch (mark!) { case RouteMark.recommended: - return const Text("Recommended", style: style); + return Text("recommended".i18n, style: style); case RouteMark.fastest: - return const Text("Fastest", style: style); + return Text("fastest".i18n, style: style); } } - Color markColor() { + Color markColor(BuildContext context) { switch (mark) { case RouteMark.recommended: - return const Color(0xff6a63d4); + return const Color.fromARGB(255, 104, 93, 255); case RouteMark.fastest: - return const Color(0xffe9d524); + return const Color.fromARGB(255, 255, 91, 146); default: - return Colors.teal; + return Theme.of(context).colorScheme.primary; } } @@ -58,14 +65,16 @@ class RouteOption extends StatelessWidget { ], )); } else { - gradeWidgets.addAll(List.generate(count, (_) => GradeDisplay(grade: i))); + 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: Colors.black.withOpacity(.5))), + child: Center( + child: Icon(Icons.add, color: Colors.black.withOpacity(.5))), )); } } @@ -77,19 +86,23 @@ class RouteOption extends StatelessWidget { child: SizedBox( width: double.infinity, child: Card( - surfaceTintColor: selected ? markColor().withOpacity(.2) : Colors.white, + surfaceTintColor: + selected ? markColor(context).withOpacity(.2) : Colors.white, margin: EdgeInsets.zero, elevation: 5, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), - side: selected ? BorderSide(color: markColor(), width: 4.0) : BorderSide.none, + side: selected + ? BorderSide(color: markColor(context), width: 4.0) + : BorderSide.none, ), child: InkWell( borderRadius: BorderRadius.circular(16.0), onTap: onSelected, child: Padding( - padding: const EdgeInsets.only(top: 16.0, bottom: 16.0, left: 20.0, right: 12.0), + padding: const EdgeInsets.only( + top: 16.0, bottom: 16.0, left: 20.0, right: 12.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -98,12 +111,14 @@ class RouteOption extends StatelessWidget { Chip( label: markLabel(), visualDensity: VisualDensity.compact, - backgroundColor: selected ? markColor() : Colors.transparent, + backgroundColor: + selected ? markColor(context) : Colors.transparent, labelPadding: const EdgeInsets.symmetric(horizontal: 8.0), - labelStyle: TextStyle(color: selected ? Colors.white : null), + labelStyle: + TextStyle(color: selected ? Colors.white : null), shape: StadiumBorder( side: BorderSide( - color: markColor(), + color: markColor(context), width: 3.0, ), ), diff --git a/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart b/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart index 3c2056f..626cdcb 100644 --- a/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart +++ b/filcnaplo_premium/lib/ui/mobile/goal_planner/test.dart @@ -1,8 +1,15 @@ +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/subject.dart'; +import 'package:filcnaplo_kreta_api/providers/grade_provider.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 @@ -12,13 +19,25 @@ enum PlanResult { } class GoalPlannerTest extends StatefulWidget { - const GoalPlannerTest({Key? key}) : super(key: key); + 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 = []; @@ -39,11 +58,14 @@ class _GoalPlannerTestState extends State { 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)); + 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; + recommended = + plans.where((e) => singleSolution ? true : e.sigma > 0).first; plans.removeWhere((e) => e == recommended); } catch (_) {} @@ -78,8 +100,18 @@ class _GoalPlannerTestState extends State { 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(); @@ -87,33 +119,89 @@ class _GoalPlannerTestState extends State { return Scaffold( body: SafeArea( child: ListView( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.only( + left: 22.0, right: 22.0, top: 5.0, bottom: 220.0), children: [ - const Align( - alignment: Alignment.topLeft, - child: BackButton(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BackButton(), + Padding( + padding: const EdgeInsets.only(right: 15.0), + child: Text( + 'goal_planner_title'.i18n, + style: const TextStyle( + fontWeight: FontWeight.w500, fontSize: 18.0), + ), + ), + ], ), const SizedBox(height: 12.0), - const Text( - "Set a goal", - style: 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()), - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + 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), - const Text( - "Pick a route", - style: TextStyle( + Text( + "pick_route".i18n, + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20.0, ), @@ -157,8 +245,9 @@ class _GoalPlannerTestState extends State { child: Container( padding: const EdgeInsets.only(top: 24.0), decoration: BoxDecoration( - color: const Color.fromARGB(255, 215, 255, 242), - borderRadius: const BorderRadius.vertical(top: Radius.circular(24.0)), + color: Colors.white, + borderRadius: + const BorderRadius.vertical(top: Radius.circular(24.0)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(.1), @@ -183,13 +272,16 @@ class _GoalPlannerTestState extends State { SizedBox( width: double.infinity, child: RawMaterialButton( - onPressed: () {}, - fillColor: const Color(0xff01342D), + 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: const Text( - "Track it!", - style: TextStyle( + child: Text( + "track_it".i18n, + style: const TextStyle( color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.w600,