some progress in goal planner (started goal state)

This commit is contained in:
Kima 2023-08-28 18:25:20 +02:00
parent 0ac0586fba
commit d524d452bd
10 changed files with 440 additions and 92 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -46,8 +46,10 @@ 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 planning // non kreta data
"goal_plans": String,
"goal_averages": String,
"goal_befores": String,
});
Future<void> createTable(Database db, DatabaseStruct struct) =>
@ -99,8 +101,10 @@ Future<Database> initDB(DatabaseProvider database) async {
"renamed_teachers": "{}",
// "subject_lesson_count": "{}", // non kreta data
"last_seen_grade": 0,
// goal plans // non kreta data
// goal planning // non kreta data
"goal_plans": "{}",
"goal_averages": "{}",
"goal_befores": "{}",
});
} catch (error) {
print("ERROR: migrateDB: $error");

View File

@ -192,6 +192,7 @@ class UserDatabaseQuery {
return lastSeen;
}
// renamed things
Future<Map<String, String>> renamedSubjects({required String userId}) async {
List<Map> userData =
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
@ -214,14 +215,36 @@ class UserDatabaseQuery {
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
// goal planner
Future<Map<String, String>> subjectGoalPlans({required String userId}) async {
List<Map> userData =
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return {};
String? goalPlansJson =
userData.elementAt(0)["goal_plans"] as String?;
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()));
}
Future<Map<String, String>> subjectGoalAverages(
{required String userId}) async {
List<Map> userData =
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return {};
String? goalAvgsJson = userData.elementAt(0)["goal_averages"] as String?;
if (goalAvgsJson == null) return {};
return (jsonDecode(goalAvgsJson) as Map)
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
Future<Map<String, String>> subjectGoalBefores(
{required String userId}) async {
List<Map> userData =
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return {};
String? goalBeforesJson = userData.elementAt(0)["goal_befores"] as String?;
if (goalBeforesJson == null) return {};
return (jsonDecode(goalBeforesJson) as Map)
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
}

View File

@ -127,6 +127,7 @@ class UserDatabaseStore {
where: "id = ?", whereArgs: [userId]);
}
// renamed things
Future<void> storeRenamedSubjects(Map<String, String> subjects,
{required String userId}) async {
String renamedSubjectsJson = jsonEncode(subjects);
@ -141,10 +142,25 @@ class UserDatabaseStore {
where: "id = ?", whereArgs: [userId]);
}
// goal planner
Future<void> storeSubjectGoalPlans(Map<String, String> plans,
{required String userId}) async {
String goalPlansJson = jsonEncode(plans);
await db.update("user_data", {"goal_plans": goalPlansJson},
where: "id = ?", whereArgs: [userId]);
}
Future<void> storeSubjectGoalAverages(Map<String, String> avgs,
{required String userId}) async {
String goalAvgsJson = jsonEncode(avgs);
await db.update("user_data", {"goal_averages": goalAvgsJson},
where: "id = ?", whereArgs: [userId]);
}
Future<void> storeSubjectGoalBefores(Map<String, String> befores,
{required String userId}) async {
String goalBeforesJson = jsonEncode(befores);
await db.update("user_data", {"goal_befores": goalBeforesJson},
where: "id = ?", whereArgs: [userId]);
}
}

View File

@ -86,6 +86,7 @@ flutter:
- assets/icons/ic_splash.png
- assets/animations/
- assets/images/
- assets/images/subject_covers/
fonts:
- family: FilcIcons

View File

@ -3,12 +3,14 @@ import 'package:flutter/material.dart';
class RoundBorderIcon extends StatelessWidget {
final Color color;
final double width;
final double padding;
final Widget icon;
const RoundBorderIcon(
{Key? key,
this.color = Colors.black,
this.width = 1.5,
this.padding = 5.0,
required this.icon})
: super(key: key);
@ -20,7 +22,7 @@ class RoundBorderIcon extends StatelessWidget {
borderRadius: BorderRadius.circular(50.0),
),
child: Padding(
padding: const EdgeInsets.all(5.0),
padding: EdgeInsets.all(padding),
child: icon,
),
);

View File

@ -26,6 +26,7 @@ import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.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/goal_planner/goal_state_screen.dart';
import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -292,12 +293,11 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
onTap: () {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) =>
GoalPlannerScreen(subject: widget.subject)));
GoalStateScreen(subject: widget.subject)));
},
child: Container(
width: 54.0,
padding: const EdgeInsets.symmetric(
horizontal: 5.0, vertical: 8.0),
padding: const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45.0),
color: Theme.of(context)

View File

@ -65,6 +65,15 @@ class _GoalPlannerScreenState extends State<GoalPlannerScreen> {
return await dbProvider.userQuery.subjectGoalPlans(userId: user.id!);
}
Future<Map<String, String>> fetchGoalAverages() async {
return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!);
}
// haha bees lol
Future<Map<String, String>> fetchGoalBees() async {
return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!);
}
PlanResult getResult() {
final currentAvg = GoalPlannerHelper.averageEvals(grades);
@ -148,7 +157,11 @@ class _GoalPlannerScreenState extends State<GoalPlannerScreen> {
body: SafeArea(
child: ListView(
padding: const EdgeInsets.only(
left: 22.0, right: 22.0, top: 5.0, bottom: 220.0),
top: 5.0,
bottom: 220.0,
right: 15.0,
left: 2.0,
),
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -215,91 +228,99 @@ class _GoalPlannerScreenState extends State<GoalPlannerScreen> {
],
),
const SizedBox(height: 12.0),
Text(
"set_a_goal".i18n,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.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),
],
),
),
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),
],
),
),
@ -346,12 +367,25 @@ class _GoalPlannerScreenState extends State<GoalPlannerScreen> {
}
final goalPlans = await fetchGoalPlans();
final goalAvgs = await fetchGoalAverages();
final goalBeforeGrades = await fetchGoalBees();
goalPlans[widget.subject.id] =
selectedRoute!.dbString;
goalAvgs[widget.subject.id] =
goalValue.toStringAsFixed(1);
goalBeforeGrades[widget.subject.id] =
avg.toStringAsFixed(1);
await dbProvider.userStore.storeSubjectGoalPlans(
goalPlans,
userId: user.id!);
await dbProvider.userStore.storeSubjectGoalAverages(
goalAvgs,
userId: user.id!);
await dbProvider.userStore.storeSubjectGoalBefores(
goalBeforeGrades,
userId: user.id!);
Navigator.of(context).pop();
},

View File

@ -0,0 +1,229 @@
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:filcnaplo_mobile_ui/common/average_display.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
import 'package:filcnaplo_mobile_ui/common/round_border_icon.dart';
import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_state_screen.i18n.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class GoalStateScreen extends StatefulWidget {
final Subject subject;
const GoalStateScreen({Key? key, required this.subject}) : super(key: key);
@override
State<GoalStateScreen> createState() => _GoalStateScreenState();
}
class _GoalStateScreenState extends State<GoalStateScreen> {
late UserProvider user;
late DatabaseProvider db;
double goalAvg = 0.0;
double beforeAvg = 0.0;
double avgDifference = 0;
@override
void initState() {
super.initState();
user = Provider.of<UserProvider>(context, listen: false);
db = Provider.of<DatabaseProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((_) {
fetchGoalAverages();
});
}
void fetchGoalAverages() async {
var goalAvgRes = await db.userQuery.subjectGoalAverages(userId: user.id!);
var beforeAvgRes = await db.userQuery.subjectGoalBefores(userId: user.id!);
String? goalAvgStr = goalAvgRes[widget.subject.id];
String? beforeAvgStr = beforeAvgRes[widget.subject.id];
goalAvg = double.parse(goalAvgStr ?? '0.0');
beforeAvg = double.parse(beforeAvgStr ?? '0.0');
avgDifference = ((goalAvg - beforeAvg) / beforeAvg.abs()) * 100;
}
@override
Widget build(BuildContext context) {
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,
],
),
),
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(),
],
),
const SizedBox(height: 22.0),
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(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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(
children: [
Text(
goalAvg.toString(),
style: const TextStyle(
height: 1.1,
fontSize: 42.0,
fontWeight: FontWeight.w800,
),
),
Center(
child: Container(
width: 54.0,
padding:
const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45.0),
color: Colors.limeAccent.shade700
.withOpacity(.15),
),
child: Text(avgDifference.toString()),
),
),
],
),
],
),
),
),
],
),
),
),
),
);
}
}

View File

@ -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",
"almost_there": "Almost there! Keep going!",
"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<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}