update packages

This commit is contained in:
BalazsManus 2025-02-13 14:15:08 +01:00
parent 22ebf816dd
commit 478edbefc0
37 changed files with 7005 additions and 6988 deletions

54
.gitignore vendored
View File

@ -1,27 +1,27 @@
# See https://www.dartlang.org/guides/libraries/private-files # See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub # Files and directories created by pub
.dart_tool/ .dart_tool/
.packages .packages
build/ build/
# If you're building an application, you may want to check-in your pubspec.lock # If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock pubspec.lock
# Directory created by dartdoc # Directory created by dartdoc
# If you don't generate documentation locally you can remove this line. # If you don't generate documentation locally you can remove this line.
doc/api/ doc/api/
# dotenv environment variables file # dotenv environment variables file
.env* .env*
# Avoid committing generated Javascript files: # Avoid committing generated Javascript files:
*.dart.js *.dart.js
*.info.json # Produced by the --dump-info flag. *.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your *.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript. # project includes source files written in JavaScript.
*.js_ *.js_
*.js.deps *.js.deps
*.js.map *.js.map
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies

1322
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
# reFilc+ ✨ # reFilc+ ✨
A collection of features only accessible for reFilc+ subscribers. A collection of features only accessible for reFilc+ subscribers.

View File

@ -1,28 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to # This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints. # check for errors, warnings, and lints.
# #
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`. # invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml` # section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints # included above or to enable additional rules. A list of all available lints
# and their documentation is published at # and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html. # https://dart-lang.github.io/linter/lints/index.html.
# #
# Instead of disabling a lint rule for the entire project in the # Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code # section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and # or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file # `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint. # producing the lint.
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View File

@ -1,201 +1,209 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:refilc/api/client.dart'; import 'package:refilc/api/client.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:refilc_plus/models/premium_result.dart'; import 'package:refilc_plus/models/premium_result.dart';
// import 'package:flutter/foundation.dart'; // import 'package:flutter/foundation.dart';
// import 'package:flutter/services.dart'; // import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
// import 'package:home_widget/home_widget.dart'; // import 'package:home_widget/home_widget.dart';
class PremiumAuth { class PremiumAuth {
final SettingsProvider _settings; final SettingsProvider _settings;
StreamSubscription? _sub; StreamSubscription? _sub;
PremiumAuth({required SettingsProvider settings}) : _settings = settings; PremiumAuth({required SettingsProvider settings}) : _settings = settings;
// initAuth() { // initAuth() {
// try { // try {
// _sub ??= uriLinkStream.listen( // _sub ??= uriLinkStream.listen(
// (Uri? uri) { // (Uri? uri) {
// if (uri != null) { // if (uri != null) {
// final accessToken = uri.queryParameters['access_token']; // final accessToken = uri.queryParameters['access_token'];
// if (accessToken != null) { // if (accessToken != null) {
// finishAuth(accessToken); // finishAuth(accessToken);
// } // }
// } // }
// }, // },
// onError: (err) { // onError: (err) {
// log("ERROR: initAuth: $err"); // log("ERROR: initAuth: $err");
// }, // },
// ); // );
// launchUrl( // launchUrl(
// Uri.parse(FilcAPI.plusAuthLogin), // Uri.parse(FilcAPI.plusAuthLogin),
// mode: LaunchMode.externalApplication, // mode: LaunchMode.externalApplication,
// ); // );
// } catch (err, sta) { // } catch (err, sta) {
// log("ERROR: initAuth: $err\n$sta"); // log("ERROR: initAuth: $err\n$sta");
// } // }
// } // }
initAuth({required String product}) { initAuth({required String product, required String paymentProvider}) {
try { try {
_sub ??= uriLinkStream.listen( _sub ??= uriLinkStream.listen(
(Uri? uri) { (Uri? uri) {
if (uri != null) { if (uri != null) {
final sessionId = uri.queryParameters['session_id']; final sessionId = uri.queryParameters['session_id'];
if (sessionId != null) { if (sessionId != null) {
finishAuth(sessionId); finishAuth(sessionId);
} }
} }
}, },
onError: (err) { onError: (err) {
log("ERROR: initAuth: $err"); log("ERROR: initAuth: $err");
}, },
); );
launchUrl( String url = "https://refilcapp.hu";
Uri.parse( if (paymentProvider == "stripe") {
"${FilcAPI.payment}/stripe-create-checkout?product=$product&rf_uinid=${_settings.xFilcId}"), url =
mode: LaunchMode.externalApplication, "${FilcAPI.payment}/stripe-create-checkout?product=$product&rf_uinid=${_settings.xFilcId}";
); } else if (paymentProvider == "paypal") {
} catch (err, sta) { url =
log("ERROR: initAuth: $err\n$sta"); "https://refilcapp.hu/payment/paypal/mobile-checkout?product=$product&device_id=${_settings.xFilcId}";
} }
}
launchUrl(
// Future<bool> finishAuth(String accessToken) async { Uri.parse(url),
// try { mode: LaunchMode.externalApplication,
// // final res = await http.get(Uri.parse( );
// // "${FilcAPI.plusScopes}?access_token=${Uri.encodeComponent(accessToken)}")); } catch (err, sta) {
// // final scopes = log("ERROR: initAuth: $err\n$sta");
// // ((jsonDecode(res.body) as Map)["scopes"] as List).cast<String>(); }
// // log("[INFO] Premium auth finish: ${scopes.join(',')}"); }
// await _settings.update(premiumAccessToken: accessToken);
// final result = await refreshAuth(); // Future<bool> finishAuth(String accessToken) async {
// // if (Platform.isAndroid) updateWidget(); // try {
// return result; // // final res = await http.get(Uri.parse(
// } catch (err, sta) { // // "${FilcAPI.plusScopes}?access_token=${Uri.encodeComponent(accessToken)}"));
// log("[ERROR] reFilc+ auth failed: $err\n$sta"); // // final scopes =
// } // // ((jsonDecode(res.body) as Map)["scopes"] as List).cast<String>();
// // log("[INFO] Premium auth finish: ${scopes.join(',')}");
// await _settings.update(premiumAccessToken: "", premiumScopes: []); // await _settings.update(premiumAccessToken: accessToken);
// // if (Platform.isAndroid) updateWidget(); // final result = await refreshAuth();
// return false; // // if (Platform.isAndroid) updateWidget();
// } // return result;
// } catch (err, sta) {
Future<bool> finishAuth(String sessionId) async { // log("[ERROR] reFilc+ auth failed: $err\n$sta");
try { // }
// final res = await http.get(Uri.parse(
// "${FilcAPI.plusScopes}?access_token=${Uri.encodeComponent(accessToken)}")); // await _settings.update(premiumAccessToken: "", premiumScopes: []);
// final scopes = // // if (Platform.isAndroid) updateWidget();
// ((jsonDecode(res.body) as Map)["scopes"] as List).cast<String>(); // return false;
// log("[INFO] Premium auth finish: ${scopes.join(',')}"); // }
await _settings.update(plusSessionId: sessionId);
final result = await refreshAuth(); Future<bool> finishAuth(String sessionId) async {
// if (Platform.isAndroid) updateWidget(); try {
return result; // final res = await http.get(Uri.parse(
} catch (err, sta) { // "${FilcAPI.plusScopes}?access_token=${Uri.encodeComponent(accessToken)}"));
log("[ERROR] reFilc+ auth failed: $err\n$sta"); // final scopes =
} // ((jsonDecode(res.body) as Map)["scopes"] as List).cast<String>();
// log("[INFO] Premium auth finish: ${scopes.join(',')}");
await _settings.update(plusSessionId: "", premiumScopes: ["refilc.plus.*"]); await _settings.update(plusSessionId: sessionId);
// if (Platform.isAndroid) updateWidget(); final result = await refreshAuth();
return false; // if (Platform.isAndroid) updateWidget();
} return result;
} catch (err, sta) {
// Future<bool?> updateWidget() async { log("[ERROR] reFilc+ auth failed: $err\n$sta");
// try { }
// return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
// } on PlatformException catch (exception) { await _settings.update(plusSessionId: "", premiumScopes: []);
// if (kDebugMode) { // if (Platform.isAndroid) updateWidget();
// print('Error Updating Widget After Auth. $exception'); return false;
// } }
// }
// return false; // Future<bool?> updateWidget() async {
// } // try {
// return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
Future<bool> refreshAuth( // } on PlatformException catch (exception) {
{bool removePremium = false, bool reactivate = false}) async { // if (kDebugMode) {
if (!removePremium) { // print('Error Updating Widget After Auth. $exception');
if (_settings.plusSessionId == "" && !reactivate) { // }
await _settings.update(premiumScopes: ["refilc.plus.*"], premiumLogin: ""); // }
return false; // return false;
} // }
// skip reFilc+ check when disconnected Future<bool> refreshAuth(
try { {bool removePremium = false, bool reactivate = false}) async {
final status = await InternetAddress.lookup('api.refilc.hu'); if (!removePremium) {
if (status.isEmpty) return false; if (_settings.plusSessionId == "" && !reactivate) {
} on SocketException catch (_) { await _settings.update(premiumScopes: [], premiumLogin: "");
return false; return false;
} }
for (int tries = 0; tries < 3; tries++) { // skip reFilc+ check when disconnected
try { try {
if (kDebugMode) { final status = await InternetAddress.lookup('api.refilc.hu');
print(FilcAPI.plusActivation); if (status.isEmpty) return false;
print(_settings.plusSessionId); } on SocketException catch (_) {
print(_settings.xFilcId); return false;
} }
final res = await http.post(Uri.parse(FilcAPI.plusActivation), body: { for (int tries = 0; tries < 3; tries++) {
"session_id": _settings.plusSessionId, try {
"rf_uinid": _settings.xFilcId, if (kDebugMode) {
}); print(FilcAPI.plusActivation);
print(_settings.plusSessionId);
if (kDebugMode) print(res.body); print(_settings.xFilcId);
}
if (res.body == "") throw "empty body";
// if (res.body == "Unauthorized") { final res = await http.post(Uri.parse(FilcAPI.plusActivation), body: {
// throw "User is not autchenticated to Github!"; "session_id": _settings.plusSessionId,
// } "rf_uinid": _settings.xFilcId,
// if (res.body == "empty_sponsors") { });
// throw "This user isn't sponsoring anyone currently!";
// } if (kDebugMode) print(res.body);
if (res.body == "expired_subscription") {
throw "This user isn't a subscriber anymore!"; if (res.body == "") throw "empty body";
} // if (res.body == "Unauthorized") {
if (res.body == "no_subscription") { // throw "User is not autchenticated to Github!";
throw "This user isn't a subscriber!"; // }
} // if (res.body == "empty_sponsors") {
if (res.body == "unknown_device") { // throw "This user isn't sponsoring anyone currently!";
throw "This device is not recognized, please contact support!"; // }
} if (res.body == "expired_subscription") {
throw "This user isn't a subscriber anymore!";
final premium = PremiumResult.fromJson(jsonDecode(res.body) as Map); }
if (res.body == "no_subscription") {
// successful activation of reFilc+ throw "This user isn't a subscriber!";
log("[INFO] reFilc+ activated: ${premium.scopes.join(',')}"); }
await _settings.update( if (res.body == "unknown_device") {
plusSessionId: premium.sessionId, throw "This device is not recognized, please contact support!";
premiumScopes: premium.scopes, }
premiumLogin: premium.login,
); final premium = PremiumResult.fromJson(jsonDecode(res.body) as Map);
return true;
} catch (err, sta) { // successful activation of reFilc+
// error while activating reFilc+ log("[INFO] reFilc+ activated: ${premium.scopes.join(',')}");
log("[ERROR] reFilc+ activation failed: $err\n$sta"); await _settings.update(
} plusSessionId: premium.sessionId,
premiumScopes: premium.scopes,
await Future.delayed(const Duration(seconds: 1)); premiumLogin: premium.login,
} );
} return true;
} catch (err, sta) {
// activation of reFilc+ failed // error while activating reFilc+
await _settings.update( log("[ERROR] reFilc+ activation failed: $err\n$sta");
premiumAccessToken: "", }
premiumScopes: ["refilc.plus.*"],
premiumLogin: "", await Future.delayed(const Duration(seconds: 1));
plusSessionId: "", }
); }
return false;
} // activation of reFilc+ failed
} await _settings.update(
premiumAccessToken: "",
premiumScopes: [],
premiumLogin: "",
plusSessionId: "",
);
return false;
}
}

View File

@ -1,60 +1,60 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dynamic_icon_plus/flutter_dynamic_icon_plus.dart'; import 'package:flutter_dynamic_icon_plus/flutter_dynamic_icon_plus.dart';
class AppIconHelper { class AppIconHelper {
// static const _channel = MethodChannel('app_icon'); // static const _channel = MethodChannel('app_icon');
static Future<void> setAppIcon(String iconName) async { static Future<void> setAppIcon(String iconName) async {
try { try {
if (await FlutterDynamicIconPlus.supportsAlternateIcons) { if (await FlutterDynamicIconPlus.supportsAlternateIcons) {
// await FlutterDynamicIconPlus.setAlternateIconName(iconName: "icon_new"); // await FlutterDynamicIconPlus.setAlternateIconName(iconName: "icon_new");
if (kDebugMode) { if (kDebugMode) {
print("successfully changed app icon"); print("successfully changed app icon");
} }
return; return;
} }
} on PlatformException { } on PlatformException {
if (kDebugMode) { if (kDebugMode) {
print("failed to change icon"); print("failed to change icon");
} }
} catch (e) { } catch (e) {
// ha nem megy hat nem megy // ha nem megy hat nem megy
} }
// if (Platform.isIOS) { // if (Platform.isIOS) {
// // change icon on ios // // change icon on ios
// try { // try {
// if (await FlutterDynamicIcon.supportsAlternateIcons) { // if (await FlutterDynamicIcon.supportsAlternateIcons) {
// await _channel.invokeMethod('changeIcon', iconName); // await _channel.invokeMethod('changeIcon', iconName);
// // await FlutterDynamicIcon.setApplicationIconBadgeNumber(0); we don't need this rn, but we will // // await FlutterDynamicIcon.setApplicationIconBadgeNumber(0); we don't need this rn, but we will
// await FlutterDynamicIcon.setAlternateIconName(iconName); // await FlutterDynamicIcon.setAlternateIconName(iconName);
// } // }
// } on PlatformException catch (e) { // } on PlatformException catch (e) {
// if (kDebugMode) { // if (kDebugMode) {
// print('Failed to change app icon: ${e.message}'); // print('Failed to change app icon: ${e.message}');
// } // }
// } catch (e) { // } catch (e) {
// if (kDebugMode) { // if (kDebugMode) {
// print('Ha nem megy, hat nem megy'); // print('Ha nem megy, hat nem megy');
// } // }
// } // }
// } else if (Platform.isAndroid) { // } else if (Platform.isAndroid) {
// // change icon on android // // change icon on android
// // ignore: no_leading_underscores_for_local_identifiers // // ignore: no_leading_underscores_for_local_identifiers
// final _androidDynamicIconPlugin = AndroidDynamicIcon(); // final _androidDynamicIconPlugin = AndroidDynamicIcon();
// await _androidDynamicIconPlugin.changeIcon( // await _androidDynamicIconPlugin.changeIcon(
// bundleId: 'hu.refilc.naplo', // bundleId: 'hu.refilc.naplo',
// isNewIcon: iconName != 'refilc_default' ? true : false, // isNewIcon: iconName != 'refilc_default' ? true : false,
// iconName: iconName != 'refilc_default' ? iconName : '', // iconName: iconName != 'refilc_default' ? iconName : '',
// iconNames: [ // iconNames: [
// 'refilc_default', // 'refilc_default',
// 'refilc_overcomplicated', // 'refilc_overcomplicated',
// 'refilc_concept', // 'refilc_concept',
// 'refilc_pride', // 'refilc_pride',
// ], // ],
// ); // );
// } else { // } else {
// // ha nem megy hát nem megy // // ha nem megy hát nem megy
// } // }
} }
} }

View File

@ -1,19 +1,19 @@
class PremiumResult { class PremiumResult {
final String sessionId; final String sessionId;
final List<String> scopes; final List<String> scopes;
final String login; final String login;
PremiumResult({ PremiumResult({
required this.sessionId, required this.sessionId,
required this.scopes, required this.scopes,
required this.login, required this.login,
}); });
factory PremiumResult.fromJson(Map json) { factory PremiumResult.fromJson(Map json) {
return PremiumResult( return PremiumResult(
sessionId: json["session_id"] ?? "", sessionId: json["session_id"] ?? "",
scopes: (json["scopes"] ?? []).cast<String>(), scopes: (json["scopes"] ?? []).cast<String>(),
login: json["customer_id"] ?? "", login: json["customer_id"] ?? "",
); );
} }
} }

View File

@ -1,51 +1,51 @@
class PremiumScopes { class PremiumScopes {
// everything // everything
static const all = "refilc.plus.*"; static const all = "refilc.plus.*";
// idk where it will be but i need it // idk where it will be but i need it
// static const renameTeachers = "refilc.plus.RENAME_TEACHERS"; // static const renameTeachers = "refilc.plus.RENAME_TEACHERS";
// static const goalPlanner = "refilc.plus.GOAL_PLANNER"; // static const goalPlanner = "refilc.plus.GOAL_PLANNER";
// static const changeAppIcon = "refilc.plus.CHANGE_APP_ICON"; // static const changeAppIcon = "refilc.plus.CHANGE_APP_ICON";
// tier 1 (Kupak) (reFilc+) // tier 1 (Kupak) (reFilc+)
static const maxTwoAccounts = "refilc.plus.MAX_TWO_ACCOUNTS"; static const maxTwoAccounts = "refilc.plus.MAX_TWO_ACCOUNTS";
static const earlyAccess = "refilc.plus.EARLY_ACCESS"; static const earlyAccess = "refilc.plus.EARLY_ACCESS";
static const totalGradeCalculator = "refilc.plus.TOTAL_GRADE_CALCULATOR"; static const totalGradeCalculator = "refilc.plus.TOTAL_GRADE_CALCULATOR";
static const welcomeMessage = "refilc.plus.WELCOME_MESSAGE"; static const welcomeMessage = "refilc.plus.WELCOME_MESSAGE";
static const unlimitedSelfNotes = "refilc.plus.UNLIMITED_SELF_NOTES"; static const unlimitedSelfNotes = "refilc.plus.UNLIMITED_SELF_NOTES";
static const customGradeRarities = "refilc.plus.CUSTOM_GRADE_RARITIES"; static const customGradeRarities = "refilc.plus.CUSTOM_GRADE_RARITIES";
static const gradeExporting = "refilc.plus.GRADE_EXPORTING"; static const gradeExporting = "refilc.plus.GRADE_EXPORTING";
// tier scope // tier scope
// static const tierCap = "refilc.plus.tier.CAP"; // static const tierCap = "refilc.plus.tier.CAP";
// tier 2 (Tinta) (reFilc+ Gold) // tier 2 (Tinta) (reFilc+ Gold)
static const noAccountLimit = "refilc.plus.NO_ACCOUNT_LIMIT"; static const noAccountLimit = "refilc.plus.NO_ACCOUNT_LIMIT";
static const appIconChange = "refilc.plus.APP_ICON_CHANGE"; static const appIconChange = "refilc.plus.APP_ICON_CHANGE";
static const liveActivityColor = "refilc.plus.LIVE_ACTIVITY_COLOR"; static const liveActivityColor = "refilc.plus.LIVE_ACTIVITY_COLOR";
static const customFont = "refilc.plus.CUSTOM_FONT"; static const customFont = "refilc.plus.CUSTOM_FONT";
static const timetableNotes = "refilc.plus.TIMETABLE_NOTES"; static const timetableNotes = "refilc.plus.TIMETABLE_NOTES";
static const unlimitedGoalPlanner = "refilc.plus.UNLIMITED_GOAL_PLANNER"; static const unlimitedGoalPlanner = "refilc.plus.UNLIMITED_GOAL_PLANNER";
static const calendarSync = "refilc.plus.CALENDAR_SYNC"; static const calendarSync = "refilc.plus.CALENDAR_SYNC";
// tier scope // tier scope
// static const tierInk = "refilc.plus.tier.INK"; // static const tierInk = "refilc.plus.tier.INK";
// tier 3 (Szivacs) // tier 3 (Szivacs)
// cancelled // cancelled
// tier scope // tier scope
static const tierSponge = "refilc.plus.tier.SPONGE"; static const tierSponge = "refilc.plus.tier.SPONGE";
// uncategorized // uncategorized
// old scopes // old scopes
static const nickname = "refilc.plus.NICKNAME"; static const nickname = "refilc.plus.NICKNAME";
static const gradeStats = "refilc.plus.GRADE_STATS"; static const gradeStats = "refilc.plus.GRADE_STATS";
static const customColors = "refilc.plus.CUSTOM_COLORS"; static const customColors = "refilc.plus.CUSTOM_COLORS";
static const customIcons = "refilc.plus.CUSTOM_ICONS"; static const customIcons = "refilc.plus.CUSTOM_ICONS";
static const renameSubjects = "refilc.plus.RENAME_SUBJECTS"; static const renameSubjects = "refilc.plus.RENAME_SUBJECTS";
static const timetableWidget = "refilc.plus.TIMETALBE_WIDGET"; static const timetableWidget = "refilc.plus.TIMETALBE_WIDGET";
static const fsTimetable = "refilc.plus.FS_TIMETABLE"; static const fsTimetable = "refilc.plus.FS_TIMETABLE";
// new new tier scopes // new new tier scopes
static const tierBasic = "refilc.plus.tier.BASIC"; static const tierBasic = "refilc.plus.tier.BASIC";
static const tierGold = "refilc.plus.tier.GOLD"; static const tierGold = "refilc.plus.tier.GOLD";
} }

View File

@ -1,68 +1,68 @@
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart'; import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class GoalProvider extends ChangeNotifier { class GoalProvider extends ChangeNotifier {
final DatabaseProvider _db; final DatabaseProvider _db;
final UserProvider _user; final UserProvider _user;
late bool _done = false; late bool _done = false;
late GradeSubject? _doneSubject; late GradeSubject? _doneSubject;
bool get hasDoneGoals => _done; bool get hasDoneGoals => _done;
GradeSubject? get doneSubject => _doneSubject; GradeSubject? get doneSubject => _doneSubject;
GoalProvider({ GoalProvider({
required DatabaseProvider database, required DatabaseProvider database,
required UserProvider user, required UserProvider user,
}) : _db = database, }) : _db = database,
_user = user; _user = user;
Future<void> fetchDone({required GradeProvider gradeProvider}) async { Future<void> fetchDone({required GradeProvider gradeProvider}) async {
var goalAvgs = await _db.userQuery.subjectGoalAverages(userId: _user.id!); var goalAvgs = await _db.userQuery.subjectGoalAverages(userId: _user.id!);
var beforeAvgs = await _db.userQuery.subjectGoalBefores(userId: _user.id!); var beforeAvgs = await _db.userQuery.subjectGoalBefores(userId: _user.id!);
List<GradeSubject> subjects = gradeProvider.grades List<GradeSubject> subjects = gradeProvider.grades
.map((e) => e.subject) .map((e) => e.subject)
.toSet() .toSet()
.toList() .toList()
..sort((a, b) => a.name.compareTo(b.name)); ..sort((a, b) => a.name.compareTo(b.name));
goalAvgs.forEach((k, v) { goalAvgs.forEach((k, v) {
if (beforeAvgs[k] == v) { if (beforeAvgs[k] == v) {
_done = true; _done = true;
_doneSubject = subjects.where((e) => e.id == k).toList()[0]; _doneSubject = subjects.where((e) => e.id == k).toList()[0];
notifyListeners(); notifyListeners();
} }
}); });
} }
void lock() { void lock() {
_done = false; _done = false;
_doneSubject = null; _doneSubject = null;
} }
Future<void> clearGoal(GradeSubject subject) async { Future<void> clearGoal(GradeSubject subject) async {
final goalPlans = await _db.userQuery.subjectGoalPlans(userId: _user.id!); final goalPlans = await _db.userQuery.subjectGoalPlans(userId: _user.id!);
final goalAvgs = await _db.userQuery.subjectGoalAverages(userId: _user.id!); final goalAvgs = await _db.userQuery.subjectGoalAverages(userId: _user.id!);
final goalBeforeGrades = final goalBeforeGrades =
await _db.userQuery.subjectGoalBefores(userId: _user.id!); await _db.userQuery.subjectGoalBefores(userId: _user.id!);
final goalPinDates = final goalPinDates =
await _db.userQuery.subjectGoalPinDates(userId: _user.id!); await _db.userQuery.subjectGoalPinDates(userId: _user.id!);
goalPlans.remove(subject.id); goalPlans.remove(subject.id);
goalAvgs.remove(subject.id); goalAvgs.remove(subject.id);
goalBeforeGrades.remove(subject.id); goalBeforeGrades.remove(subject.id);
goalPinDates.remove(subject.id); goalPinDates.remove(subject.id);
await _db.userStore.storeSubjectGoalPlans(goalPlans, userId: _user.id!); await _db.userStore.storeSubjectGoalPlans(goalPlans, userId: _user.id!);
await _db.userStore.storeSubjectGoalAverages(goalAvgs, userId: _user.id!); await _db.userStore.storeSubjectGoalAverages(goalAvgs, userId: _user.id!);
await _db.userStore await _db.userStore
.storeSubjectGoalBefores(goalBeforeGrades, userId: _user.id!); .storeSubjectGoalBefores(goalBeforeGrades, userId: _user.id!);
await _db.userStore await _db.userStore
.storeSubjectGoalPinDates(goalPinDates, userId: _user.id!); .storeSubjectGoalPinDates(goalPinDates, userId: _user.id!);
} }
} }

View File

@ -1,31 +1,31 @@
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_plus/api/auth.dart'; import 'package:refilc_plus/api/auth.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class PlusProvider extends ChangeNotifier { class PlusProvider extends ChangeNotifier {
final SettingsProvider _settings; final SettingsProvider _settings;
List<String> get scopes => _settings.premiumScopes; List<String> get scopes => _settings.premiumScopes;
// bool hasScope(String scope) => false; // bool hasScope(String scope) => false;
bool hasScope(String scope) => bool hasScope(String scope) =>
scopes.contains(scope) || scopes.contains(PremiumScopes.all); scopes.contains(scope) || scopes.contains(PremiumScopes.all);
String get accessToken => _settings.premiumAccessToken; String get accessToken => _settings.premiumAccessToken;
String get login => _settings.premiumLogin; String get login => _settings.premiumLogin;
bool get hasPremium => bool get hasPremium =>
_settings.plusSessionId != "" && _settings.premiumScopes.isNotEmpty; _settings.plusSessionId != "" && _settings.premiumScopes.isNotEmpty;
late final PremiumAuth _auth; late final PremiumAuth _auth;
PremiumAuth get auth => _auth; PremiumAuth get auth => _auth;
PlusProvider({required SettingsProvider settings}) : _settings = settings { PlusProvider({required SettingsProvider settings}) : _settings = settings {
_auth = PremiumAuth(settings: _settings); _auth = PremiumAuth(settings: _settings);
_settings.addListener(() { _settings.addListener(() {
notifyListeners(); notifyListeners();
}); });
} }
Future<void> activate({bool removePremium = false}) async { Future<void> activate({bool removePremium = false}) async {
await _auth.refreshAuth(removePremium: removePremium); await _auth.refreshAuth(removePremium: removePremium);
notifyListeners(); notifyListeners();
} }
} }

View File

@ -1,253 +1,253 @@
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_mobile_ui/common/average_display.dart'; import 'package:refilc_mobile_ui/common/average_display.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_state_screen.i18n.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_state_screen.i18n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class GoalCompleteModal extends StatelessWidget { class GoalCompleteModal extends StatelessWidget {
const GoalCompleteModal( const GoalCompleteModal(
this.subject, { this.subject, {
super.key, super.key,
required this.user, required this.user,
required this.database, required this.database,
required this.goalAverage, required this.goalAverage,
required this.beforeAverage, required this.beforeAverage,
required this.averageDifference, required this.averageDifference,
}); });
final UserProvider user; final UserProvider user;
final DatabaseProvider database; final DatabaseProvider database;
final GradeSubject subject; final GradeSubject subject;
final double goalAverage; final double goalAverage;
final double beforeAverage; final double beforeAverage;
final double averageDifference; final double averageDifference;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
elevation: 0, elevation: 0,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
child: Container( child: Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(20.0), borderRadius: BorderRadius.circular(20.0),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
image: const DecorationImage( image: const DecorationImage(
image: AssetImage('assets/images/static_confetti.png'), image: AssetImage('assets/images/static_confetti.png'),
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
), ),
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
), ),
padding: const EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
goalAverage.toStringAsFixed(1), goalAverage.toStringAsFixed(1),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 64.0, fontSize: 64.0,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
// const SizedBox(width: 10.0), // const SizedBox(width: 10.0),
// Icon( // Icon(
// SubjectIcon.resolveVariant( // SubjectIcon.resolveVariant(
// subject: subject, context: context), // subject: subject, context: context),
// color: Colors.white, // color: Colors.white,
// size: 64.0, // size: 64.0,
// ), // ),
], ],
), ),
), ),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
Text( Text(
'congrats_title'.i18n, 'congrats_title'.i18n,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 27.0, fontSize: 27.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
height: 1.2, height: 1.2,
color: AppColors.of(context).text, color: AppColors.of(context).text,
), ),
), ),
Text( Text(
'goal_reached'.i18n.fill(['20']), 'goal_reached'.i18n.fill(['20']),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 15.0, fontSize: 15.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.1, height: 1.1,
color: AppColors.of(context).text, color: AppColors.of(context).text,
), ),
), ),
const SizedBox(height: 18.0), const SizedBox(height: 18.0),
Column( Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'started_at'.i18n, 'started_at'.i18n,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 17.0, fontSize: 17.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text, color: AppColors.of(context).text,
), ),
), ),
const SizedBox(width: 5.0), const SizedBox(width: 5.0),
AverageDisplay( AverageDisplay(
average: beforeAverage, average: beforeAverage,
), ),
], ],
), ),
Text( Text(
'improved_by'.i18n.fill([ 'improved_by'.i18n.fill([
'${averageDifference.toStringAsFixed(2)}%', '${averageDifference.toStringAsFixed(2)}%',
]), ]),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 17.0, fontSize: 17.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text, color: AppColors.of(context).text,
), ),
), ),
], ],
), ),
const SizedBox(height: 20.0), const SizedBox(height: 20.0),
Column( Column(
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Hamarosan...")), const SnackBar(content: Text("Hamarosan...")),
); );
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
gradient: const LinearGradient( gradient: const LinearGradient(
colors: [ colors: [
Color(0xFFCAECFA), Color(0xFFCAECFA),
Color(0xFFF4D9EE), Color(0xFFF4D9EE),
Color(0xFFF3EFDA), Color(0xFFF3EFDA),
], ],
stops: [0.0, 0.53, 1.0], stops: [0.0, 0.53, 1.0],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
'detailed_stats'.i18n, 'detailed_stats'.i18n,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Color(0xFF691A9B), color: Color(0xFF691A9B),
), ),
), ),
), ),
), ),
), ),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
color: const Color.fromARGB(38, 131, 131, 131), color: const Color.fromARGB(38, 131, 131, 131),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
'later'.i18n, 'later'.i18n,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColors.of(context).text, color: AppColors.of(context).text,
), ),
), ),
), ),
), ),
), ),
], ],
), ),
], ],
), ),
), ),
); );
// return Padding( // return Padding(
// padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 32.0), // padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 32.0),
// child: Material( // child: Material(
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// child: Padding( // child: Padding(
// padding: const EdgeInsets.all(12.0), // padding: const EdgeInsets.all(12.0),
// child: Column( // child: Column(
// children: [ // children: [
// // content or idk // // content or idk
// ], // ],
// ), // ),
// ), // ),
// ), // ),
// ); // );
} }
static Future<T?> show<T>( static Future<T?> show<T>(
GradeSubject subject, { GradeSubject subject, {
required BuildContext context, required BuildContext context,
}) async { }) async {
UserProvider user = Provider.of<UserProvider>(context, listen: false); UserProvider user = Provider.of<UserProvider>(context, listen: false);
DatabaseProvider db = Provider.of<DatabaseProvider>(context, listen: false); DatabaseProvider db = Provider.of<DatabaseProvider>(context, listen: false);
var goalAvgRes = await db.userQuery.subjectGoalAverages(userId: user.id!); var goalAvgRes = await db.userQuery.subjectGoalAverages(userId: user.id!);
var beforeAvgRes = await db.userQuery.subjectGoalBefores(userId: user.id!); var beforeAvgRes = await db.userQuery.subjectGoalBefores(userId: user.id!);
//DateTime goalPinDate = DateTime.parse((await db.userQuery.subjectGoalPinDates(userId: user.id!))[widget.subject.id]!); //DateTime goalPinDate = DateTime.parse((await db.userQuery.subjectGoalPinDates(userId: user.id!))[widget.subject.id]!);
String? goalAvgStr = goalAvgRes[subject.id]; String? goalAvgStr = goalAvgRes[subject.id];
String? beforeAvgStr = beforeAvgRes[subject.id]; String? beforeAvgStr = beforeAvgRes[subject.id];
double goalAvg = double.parse(goalAvgStr ?? '0.0'); double goalAvg = double.parse(goalAvgStr ?? '0.0');
double beforeAvg = double.parse(beforeAvgStr ?? '0.0'); double beforeAvg = double.parse(beforeAvgStr ?? '0.0');
double avgDifference = ((goalAvg - beforeAvg) / beforeAvg.abs()) * 100; double avgDifference = ((goalAvg - beforeAvg) / beforeAvg.abs()) * 100;
return showDialog<T?>( return showDialog<T?>(
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
context: context, context: context,
builder: (context) => GoalCompleteModal( builder: (context) => GoalCompleteModal(
subject, subject,
user: user, user: user,
database: db, database: db,
goalAverage: goalAvg, goalAverage: goalAvg,
beforeAverage: beforeAvg, beforeAverage: beforeAvg,
averageDifference: avgDifference, averageDifference: avgDifference,
), ),
barrierDismissible: false, barrierDismissible: false,
); );
} }
} }

View File

@ -1,204 +1,204 @@
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class GoalInput extends StatelessWidget { class GoalInput extends StatelessWidget {
const GoalInput( const GoalInput(
{super.key, {super.key,
required this.currentAverage, required this.currentAverage,
required this.value, required this.value,
required this.onChanged}); required this.onChanged});
final double currentAverage; final double currentAverage;
final double value; final double value;
final void Function(double value) onChanged; final void Function(double value) onChanged;
void offsetToValue(Offset offset, Size size) { void offsetToValue(Offset offset, Size size) {
double v = ((offset.dx / size.width * 4 + 1) * 10).round() / 10; double v = ((offset.dx / size.width * 4 + 1) * 10).round() / 10;
v = v.clamp(1.5, 5); v = v.clamp(1.5, 5);
v = v.clamp(((currentAverage * 10).round() / 10), 5); v = v.clamp(((currentAverage * 10).round() / 10), 5);
setValue(v); setValue(v);
} }
void setValue(double v) { void setValue(double v) {
if (v != value) { if (v != value) {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
} }
onChanged(v); onChanged(v);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SettingsProvider settings = Provider.of<SettingsProvider>(context); SettingsProvider settings = Provider.of<SettingsProvider>(context);
List<int> presets = [2, 3, 4, 5]; List<int> presets = [2, 3, 4, 5];
presets = presets.where((e) => gradeToAvg(e) > currentAverage).toList(); presets = presets.where((e) => gradeToAvg(e) > currentAverage).toList();
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
LayoutBuilder(builder: (context, size) { LayoutBuilder(builder: (context, size) {
return GestureDetector( return GestureDetector(
onTapDown: (details) { onTapDown: (details) {
offsetToValue(details.localPosition, size.biggest); offsetToValue(details.localPosition, size.biggest);
}, },
onHorizontalDragUpdate: (details) { onHorizontalDragUpdate: (details) {
offsetToValue(details.localPosition, size.biggest); offsetToValue(details.localPosition, size.biggest);
}, },
child: SizedBox( child: SizedBox(
height: 32.0, height: 32.0,
width: double.infinity, width: double.infinity,
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 20.0), padding: const EdgeInsets.only(right: 20.0),
child: CustomPaint( child: CustomPaint(
painter: GoalSliderPainter( painter: GoalSliderPainter(
value: (value - 1) / 4, value: (value - 1) / 4,
settings: settings, settings: settings,
goalValue: value), goalValue: value),
), ),
), ),
), ),
); );
}), }),
// const SizedBox(height: 12.0), // const SizedBox(height: 12.0),
// Row( // Row(
// mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
// children: presets.map((e) { // children: presets.map((e) {
// final pv = (value * 10).round() / 10; // final pv = (value * 10).round() / 10;
// final selected = gradeToAvg(e) == pv; // final selected = gradeToAvg(e) == pv;
// return Padding( // return Padding(
// padding: const EdgeInsets.symmetric(horizontal: 12.0), // padding: const EdgeInsets.symmetric(horizontal: 12.0),
// child: Container( // child: Container(
// decoration: BoxDecoration( // decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(99.0), // borderRadius: BorderRadius.circular(99.0),
// color: // color:
// gradeColor(e, settings).withOpacity(selected ? 1.0 : 0.2), // gradeColor(e, settings).withOpacity(selected ? 1.0 : 0.2),
// border: Border.all(color: gradeColor(e, settings), width: 4), // border: Border.all(color: gradeColor(e, settings), width: 4),
// ), // ),
// child: Material( // child: Material(
// type: MaterialType.transparency, // type: MaterialType.transparency,
// child: InkWell( // child: InkWell(
// borderRadius: BorderRadius.circular(99.0), // borderRadius: BorderRadius.circular(99.0),
// onTap: () => setValue(gradeToAvg(e)), // onTap: () => setValue(gradeToAvg(e)),
// child: Padding( // child: Padding(
// padding: const EdgeInsets.symmetric( // padding: const EdgeInsets.symmetric(
// vertical: 2.0, horizontal: 24.0), // vertical: 2.0, horizontal: 24.0),
// child: Text( // child: Text(
// e.toString(), // e.toString(),
// style: TextStyle( // style: TextStyle(
// color: // color:
// selected ? Colors.white : gradeColor(e, settings), // selected ? Colors.white : gradeColor(e, settings),
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// fontSize: 24.0, // fontSize: 24.0,
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ); // );
// }).toList(), // }).toList(),
// ) // )
], ],
); );
} }
} }
class GoalSliderPainter extends CustomPainter { class GoalSliderPainter extends CustomPainter {
final double value; final double value;
final SettingsProvider settings; final SettingsProvider settings;
final double goalValue; final double goalValue;
GoalSliderPainter( GoalSliderPainter(
{required this.value, required this.settings, required this.goalValue}); {required this.value, required this.settings, required this.goalValue});
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final radius = size.height / 2; final radius = size.height / 2;
const cpadding = 4; const cpadding = 4;
final rect = Rect.fromLTWH(0, 0, size.width + radius, size.height); final rect = Rect.fromLTWH(0, 0, size.width + radius, size.height);
// final vrect = Rect.fromLTWH(0, 0, size.width * value + radius, size.height); // final vrect = Rect.fromLTWH(0, 0, size.width * value + radius, size.height);
canvas.drawRRect( canvas.drawRRect(
RRect.fromRectAndRadius( RRect.fromRectAndRadius(
rect, rect,
const Radius.circular(99.0), const Radius.circular(99.0),
), ),
Paint()..color = Colors.black.withOpacity(.1), Paint()..color = Colors.black.withOpacity(.1),
); );
canvas.drawRRect( canvas.drawRRect(
RRect.fromRectAndRadius( RRect.fromRectAndRadius(
rect, rect,
const Radius.circular(99.0), const Radius.circular(99.0),
), ),
Paint() Paint()
..shader = LinearGradient(colors: [ ..shader = LinearGradient(colors: [
settings.gradeColors[0], settings.gradeColors[0],
settings.gradeColors[1], settings.gradeColors[1],
settings.gradeColors[2], settings.gradeColors[2],
settings.gradeColors[3], settings.gradeColors[3],
settings.gradeColors[4], settings.gradeColors[4],
]).createShader(rect), ]).createShader(rect),
); );
double w = size.width + radius; double w = size.width + radius;
canvas.drawRRect( canvas.drawRRect(
RRect.fromRectAndRadius( RRect.fromRectAndRadius(
Rect.fromLTWH( Rect.fromLTWH(
(w - (w * 0.986)) / 2, (w - (w * 0.986)) / 2,
(size.height - (size.height * 0.85)) / 2, (size.height - (size.height * 0.85)) / 2,
w * 0.986, w * 0.986,
size.height * 0.85), size.height * 0.85),
const Radius.circular(99.0), const Radius.circular(99.0),
), ),
Paint()..color = Colors.white.withOpacity(.8), Paint()..color = Colors.white.withOpacity(.8),
); );
canvas.drawOval( canvas.drawOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(size.width * value, size.height / 2), center: Offset(size.width * value, size.height / 2),
radius: radius - cpadding), radius: radius - cpadding),
Paint()..color = Colors.white, Paint()..color = Colors.white,
); );
canvas.drawOval( canvas.drawOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(size.width * value, size.height / 2), center: Offset(size.width * value, size.height / 2),
radius: (radius - cpadding) * 0.8), radius: (radius - cpadding) * 0.8),
Paint()..color = gradeColor(goalValue.round(), settings), Paint()..color = gradeColor(goalValue.round(), settings),
); );
for (int i = 1; i < 4; i++) { for (int i = 1; i < 4; i++) {
canvas.drawOval( canvas.drawOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(size.width / 4 * i, size.height / 2), radius: 4), center: Offset(size.width / 4 * i, size.height / 2), radius: 4),
Paint()..color = Colors.white.withOpacity(.6), Paint()..color = Colors.white.withOpacity(.6),
); );
} }
} }
@override @override
bool shouldRepaint(GoalSliderPainter oldDelegate) { bool shouldRepaint(GoalSliderPainter oldDelegate) {
return oldDelegate.value != value; return oldDelegate.value != value;
} }
} }
double gradeToAvg(int grade) { double gradeToAvg(int grade) {
return grade - 0.5; return grade - 0.5;
} }
Color gradeColor(int grade, SettingsProvider settings) { Color gradeColor(int grade, SettingsProvider settings) {
// return [ // return [
// const Color(0xffFF3B30), // const Color(0xffFF3B30),
// const Color(0xffFF9F0A), // const Color(0xffFF9F0A),
// const Color(0xffFFD60A), // const Color(0xffFFD60A),
// const Color(0xff34C759), // const Color(0xff34C759),
// const Color(0xff247665), // const Color(0xff247665),
// ].elementAt(grade.clamp(1, 5) - 1); // ].elementAt(grade.clamp(1, 5) - 1);
return [ return [
settings.gradeColors[0], settings.gradeColors[0],
settings.gradeColors[1], settings.gradeColors[1],
settings.gradeColors[2], settings.gradeColors[2],
settings.gradeColors[3], settings.gradeColors[3],
settings.gradeColors[4], settings.gradeColors[4],
].elementAt(grade.clamp(1, 5) - 1); ].elementAt(grade.clamp(1, 5) - 1);
} }

View File

@ -1,191 +1,191 @@
/* /*
* Maintainer: DarK * Maintainer: DarK
* Translated from C version * Translated from C version
* Minimal Working Fixed @ 2022.12.25 * Minimal Working Fixed @ 2022.12.25
* ##Please do NOT modify if you don't know whats going on## * ##Please do NOT modify if you don't know whats going on##
* *
* Issue: #59 * Issue: #59
* *
* Future changes / ideas: * Future changes / ideas:
* - `best` should be configurable * - `best` should be configurable
*/ */
import 'dart:math'; import 'dart:math';
import 'package:refilc_kreta_api/models/category.dart'; import 'package:refilc_kreta_api/models/category.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/models/teacher.dart'; import 'package:refilc_kreta_api/models/teacher.dart';
import 'package:flutter/foundation.dart' show listEquals; import 'package:flutter/foundation.dart' show listEquals;
/// Generate list of grades that achieve the wanted goal. /// Generate list of grades that achieve the wanted goal.
/// After generating possible options, it (when doing so would NOT result in empty list) filters with two criteria: /// After generating possible options, it (when doing so would NOT result in empty list) filters with two criteria:
/// - Plan should not contain more than 15 grades /// - Plan should not contain more than 15 grades
/// - Plan should not contain only one type of grade /// - Plan should not contain only one type of grade
/// ///
/// **Usage**: /// **Usage**:
/// ///
/// ```dart /// ```dart
/// List<int> GoalPlanner(double goal, List<Grade> grades).solve().plan /// List<int> GoalPlanner(double goal, List<Grade> grades).solve().plan
/// ``` /// ```
class GoalPlanner { class GoalPlanner {
final double goal; final double goal;
final List<Grade> grades; final List<Grade> grades;
List<Plan> plans = []; List<Plan> plans = [];
GoalPlanner(this.goal, this.grades); GoalPlanner(this.goal, this.grades);
bool _allowed(int grade) => grade > goal; bool _allowed(int grade) => grade > goal;
void _generate(Generator g) { void _generate(Generator g) {
// Exit condition 1: Generator has working plan. // Exit condition 1: Generator has working plan.
if (g.currentAvg.avg >= goal) { if (g.currentAvg.avg >= goal) {
plans.add(Plan(g.plan)); plans.add(Plan(g.plan));
return; return;
} }
// Exit condition 2: Generator plan will never work. // Exit condition 2: Generator plan will never work.
if (!_allowed(g.gradeToAdd)) { if (!_allowed(g.gradeToAdd)) {
return; return;
} }
for (int i = g.max; i >= 0; i--) { for (int i = g.max; i >= 0; i--) {
int newGradeToAdd = g.gradeToAdd - 1; int newGradeToAdd = g.gradeToAdd - 1;
List<int> newPlan = List<int> newPlan =
GoalPlannerHelper._addToList<int>(g.plan, g.gradeToAdd, i); GoalPlannerHelper._addToList<int>(g.plan, g.gradeToAdd, i);
Avg newAvg = GoalPlannerHelper._addToAvg(g.currentAvg, g.gradeToAdd, i); Avg newAvg = GoalPlannerHelper._addToAvg(g.currentAvg, g.gradeToAdd, i);
int newN = GoalPlannerHelper.howManyNeeded( int newN = GoalPlannerHelper.howManyNeeded(
newGradeToAdd, newGradeToAdd,
grades + grades +
newPlan newPlan
.map((e) => Grade( .map((e) => Grade(
id: '', id: '',
date: DateTime(0), date: DateTime(0),
value: GradeValue(e, '', '', 100), value: GradeValue(e, '', '', 100),
teacher: Teacher.fromString(''), teacher: Teacher.fromString(''),
description: '', description: '',
form: '', form: '',
groupId: '', groupId: '',
type: GradeType.midYear, type: GradeType.midYear,
subject: GradeSubject.fromJson({}), subject: GradeSubject.fromJson({}),
mode: Category.fromJson({}), mode: Category.fromJson({}),
seenDate: DateTime(0), seenDate: DateTime(0),
writeDate: DateTime(0), writeDate: DateTime(0),
)) ))
.toList(), .toList(),
goal); goal);
_generate(Generator(newGradeToAdd, newN, newAvg, newPlan)); _generate(Generator(newGradeToAdd, newN, newAvg, newPlan));
} }
} }
List<Plan> solve() { List<Plan> solve() {
_generate( _generate(
Generator( Generator(
5, 5,
GoalPlannerHelper.howManyNeeded( GoalPlannerHelper.howManyNeeded(
5, 5,
grades, grades,
goal, goal,
), ),
Avg(GoalPlannerHelper.averageEvals(grades), Avg(GoalPlannerHelper.averageEvals(grades),
GoalPlannerHelper.weightSum(grades)), GoalPlannerHelper.weightSum(grades)),
[], [],
), ),
); );
// Calculate Statistics // Calculate Statistics
for (var e in plans) { for (var e in plans) {
e.sum = e.plan.fold(0, (int a, b) => a + b); e.sum = e.plan.fold(0, (int a, b) => a + b);
e.avg = e.sum / e.plan.length; e.avg = e.sum / e.plan.length;
e.sigma = sqrt( e.sigma = sqrt(
e.plan.map((i) => pow(i - e.avg, 2)).fold(0, (num a, b) => a + b) / e.plan.map((i) => pow(i - e.avg, 2)).fold(0, (num a, b) => a + b) /
e.plan.length); e.plan.length);
} }
// filter without aggression // filter without aggression
if (plans.where((e) => e.plan.length < 30).isNotEmpty) { if (plans.where((e) => e.plan.length < 30).isNotEmpty) {
plans.removeWhere((e) => !(e.plan.length < 30)); plans.removeWhere((e) => !(e.plan.length < 30));
} }
if (plans.where((e) => e.sigma > 1).isNotEmpty) { if (plans.where((e) => e.sigma > 1).isNotEmpty) {
plans.removeWhere((e) => !(e.sigma > 1)); plans.removeWhere((e) => !(e.sigma > 1));
} }
return plans; return plans;
} }
} }
class Avg { class Avg {
final double avg; final double avg;
final double n; final double n;
Avg(this.avg, this.n); Avg(this.avg, this.n);
} }
class Generator { class Generator {
final int gradeToAdd; final int gradeToAdd;
final int max; final int max;
final Avg currentAvg; final Avg currentAvg;
final List<int> plan; final List<int> plan;
Generator(this.gradeToAdd, this.max, this.currentAvg, this.plan); Generator(this.gradeToAdd, this.max, this.currentAvg, this.plan);
} }
class Plan { class Plan {
final List<int> plan; final List<int> plan;
int sum = 0; int sum = 0;
double avg = 0; double avg = 0;
int med = 0; // currently int med = 0; // currently
int mod = 0; // unused int mod = 0; // unused
double sigma = 0; double sigma = 0;
Plan(this.plan); Plan(this.plan);
String get dbString { String get dbString {
var finalString = ''; var finalString = '';
for (var i in plan) { for (var i in plan) {
finalString += "$i,"; finalString += "$i,";
} }
return finalString; return finalString;
} }
@override @override
bool operator ==(other) => other is Plan && listEquals(plan, other.plan); bool operator ==(other) => other is Plan && listEquals(plan, other.plan);
@override @override
int get hashCode => Object.hashAll(plan); int get hashCode => Object.hashAll(plan);
} }
class GoalPlannerHelper { class GoalPlannerHelper {
static Avg _addToAvg(Avg base, int grade, int n) => static Avg _addToAvg(Avg base, int grade, int n) =>
Avg((base.avg * base.n + grade * n) / (base.n + n), base.n + n); Avg((base.avg * base.n + grade * n) / (base.n + n), base.n + n);
static List<T> _addToList<T>(List<T> l, T e, int n) { static List<T> _addToList<T>(List<T> l, T e, int n) {
if (n == 0) return l; if (n == 0) return l;
List<T> tmp = l; List<T> tmp = l;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
tmp = tmp + [e]; tmp = tmp + [e];
} }
return tmp; return tmp;
} }
static int howManyNeeded(int grade, List<Grade> base, double goal) { static int howManyNeeded(int grade, List<Grade> base, double goal) {
double avg = averageEvals(base); double avg = averageEvals(base);
double wsum = weightSum(base); double wsum = weightSum(base);
if (avg >= goal) return 0; if (avg >= goal) return 0;
if (grade * 1.0 == goal) return -1; if (grade * 1.0 == goal) return -1;
int candidate = (wsum * (avg - goal) / (goal - grade)).floor(); int candidate = (wsum * (avg - goal) / (goal - grade)).floor();
return (candidate * grade + avg * wsum) / (candidate + wsum) < goal return (candidate * grade + avg * wsum) / (candidate + wsum) < goal
? candidate + 1 ? candidate + 1
: candidate; : candidate;
} }
static double averageEvals(List<Grade> grades, {bool finalAvg = false}) { static double averageEvals(List<Grade> grades, {bool finalAvg = false}) {
double average = grades double average = grades
.map((e) => e.value.value * e.value.weight / 100.0) .map((e) => e.value.value * e.value.weight / 100.0)
.fold(0.0, (double a, double b) => a + b) / .fold(0.0, (double a, double b) => a + b) /
weightSum(grades, finalAvg: finalAvg); weightSum(grades, finalAvg: finalAvg);
return average.isNaN ? 0.0 : average; return average.isNaN ? 0.0 : average;
} }
static double weightSum(List<Grade> grades, {bool finalAvg = false}) => grades static double weightSum(List<Grade> grades, {bool finalAvg = false}) => grades
.map((e) => finalAvg ? 1 : e.value.weight / 100) .map((e) => finalAvg ? 1 : e.value.weight / 100)
.fold(0, (a, b) => a + b); .fold(0, (a, b) => a + b);
} }

View File

@ -1,453 +1,453 @@
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/average_helper.dart'; import 'package:refilc/helpers/average_helper.dart';
import 'package:refilc/helpers/subject.dart'; import 'package:refilc/helpers/subject.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/group_average.dart'; import 'package:refilc_kreta_api/models/group_average.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/providers/grade_provider.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/average_display.dart';
import 'package:refilc_mobile_ui/common/round_border_icon.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_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/plus_provider.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_input.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.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_planner_screen.i18n.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_track_popup.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:refilc_plus/ui/mobile/goal_planner/route_option.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
enum PlanResult { enum PlanResult {
available, // There are possible solutions available, // There are possible solutions
unreachable, // The solutions are too hard don't even try unreachable, // The solutions are too hard don't even try
unsolvable, // There are no solutions unsolvable, // There are no solutions
reached, // Goal already reached reached, // Goal already reached
} }
class GoalPlannerScreen extends StatefulWidget { class GoalPlannerScreen extends StatefulWidget {
final GradeSubject subject; final GradeSubject subject;
const GoalPlannerScreen({super.key, required this.subject}); const GoalPlannerScreen({super.key, required this.subject});
@override @override
State<GoalPlannerScreen> createState() => _GoalPlannerScreenState(); State<GoalPlannerScreen> createState() => _GoalPlannerScreenState();
} }
class _GoalPlannerScreenState extends State<GoalPlannerScreen> { class _GoalPlannerScreenState extends State<GoalPlannerScreen> {
late GradeProvider gradeProvider; late GradeProvider gradeProvider;
late GradeCalculatorProvider calculatorProvider; late GradeCalculatorProvider calculatorProvider;
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
late DatabaseProvider dbProvider; late DatabaseProvider dbProvider;
late UserProvider user; late UserProvider user;
bool gradeCalcMode = false; bool gradeCalcMode = false;
List<Grade> getSubjectGrades(GradeSubject subject) => !gradeCalcMode List<Grade> getSubjectGrades(GradeSubject subject) => !gradeCalcMode
? gradeProvider.grades.where((e) => e.subject == subject).toList() ? gradeProvider.grades.where((e) => e.subject == subject).toList()
: calculatorProvider.grades.where((e) => e.subject == subject).toList(); : calculatorProvider.grades.where((e) => e.subject == subject).toList();
double goalValue = 4.0; double goalValue = 4.0;
List<Grade> grades = []; List<Grade> grades = [];
Plan? recommended; Plan? recommended;
Plan? fastest; Plan? fastest;
Plan? selectedRoute; Plan? selectedRoute;
List<Plan> otherPlans = []; List<Plan> otherPlans = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
user = Provider.of<UserProvider>(context, listen: false); user = Provider.of<UserProvider>(context, listen: false);
dbProvider = Provider.of<DatabaseProvider>(context, listen: false); dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
} }
Future<Map<String, String>> fetchGoalPlans() async { Future<Map<String, String>> fetchGoalPlans() async {
return await dbProvider.userQuery.subjectGoalPlans(userId: user.id!); return await dbProvider.userQuery.subjectGoalPlans(userId: user.id!);
} }
Future<Map<String, String>> fetchGoalAverages() async { Future<Map<String, String>> fetchGoalAverages() async {
return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!); return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!);
} }
// haha bees lol // haha bees lol
Future<Map<String, String>> fetchGoalBees() async { Future<Map<String, String>> fetchGoalBees() async {
return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!); return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!);
} }
Future<Map<String, String>> fetchGoalPinDates() async { Future<Map<String, String>> fetchGoalPinDates() async {
return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!); return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!);
} }
PlanResult getResult() { PlanResult getResult() {
final currentAvg = GoalPlannerHelper.averageEvals(grades); final currentAvg = GoalPlannerHelper.averageEvals(grades);
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
if (currentAvg >= goalValue) return PlanResult.reached; if (currentAvg >= goalValue) return PlanResult.reached;
final planner = GoalPlanner(goalValue, grades); final planner = GoalPlanner(goalValue, grades);
final plans = planner.solve(); final plans = planner.solve();
plans.sort((a, b) => (a.avg - (2 * goalValue + 5) / 3) plans.sort((a, b) => (a.avg - (2 * goalValue + 5) / 3)
.abs() .abs()
.compareTo(b.avg - (2 * goalValue + 5) / 3)); .compareTo(b.avg - (2 * goalValue + 5) / 3));
try { try {
final singleSolution = plans.every((e) => e.sigma == 0); final singleSolution = plans.every((e) => e.sigma == 0);
recommended = recommended =
plans.where((e) => singleSolution ? true : e.sigma > 0).first; plans.where((e) => singleSolution ? true : e.sigma > 0).first;
plans.removeWhere((e) => e == recommended); plans.removeWhere((e) => e == recommended);
} catch (_) {} } catch (_) {}
plans.sort((a, b) => a.plan.length.compareTo(b.plan.length)); plans.sort((a, b) => a.plan.length.compareTo(b.plan.length));
try { try {
fastest = plans.removeAt(0); fastest = plans.removeAt(0);
} catch (_) {} } catch (_) {}
// print((recommended?.plan.length ?? 0).toString() + '-kuki'); // print((recommended?.plan.length ?? 0).toString() + '-kuki');
// print((fastest?.plan.length ?? 0).toString() + '--asd'); // print((fastest?.plan.length ?? 0).toString() + '--asd');
if ((((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0)) >= if ((((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0)) >=
5) && 5) &&
fastest != null) { fastest != null) {
recommended = fastest; recommended = fastest;
} }
if (recommended == null) { if (recommended == null) {
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
selectedRoute = null; selectedRoute = null;
return PlanResult.unsolvable; return PlanResult.unsolvable;
} }
// print(recommended!.plan.length.toString() + '--------'); // print(recommended!.plan.length.toString() + '--------');
if (recommended!.plan.length > 20) { if (recommended!.plan.length > 20) {
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
selectedRoute = null; selectedRoute = null;
return PlanResult.unreachable; return PlanResult.unreachable;
} }
otherPlans = List.from(plans); otherPlans = List.from(plans);
// only save 2 items if not plus member // only save 2 items if not plus member
if (!Provider.of<PlusProvider>(context) if (!Provider.of<PlusProvider>(context)
.hasScope(PremiumScopes.unlimitedGoalPlanner)) { .hasScope(PremiumScopes.unlimitedGoalPlanner)) {
if (otherPlans.length > 2) { if (otherPlans.length > 2) {
otherPlans.removeRange(2, otherPlans.length - 1); otherPlans.removeRange(2, otherPlans.length - 1);
} }
} }
return PlanResult.available; return PlanResult.available;
} }
void getGrades() { void getGrades() {
grades = getSubjectGrades(widget.subject).toList(); grades = getSubjectGrades(widget.subject).toList();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
gradeProvider = Provider.of<GradeProvider>(context); gradeProvider = Provider.of<GradeProvider>(context);
calculatorProvider = Provider.of<GradeCalculatorProvider>(context); calculatorProvider = Provider.of<GradeCalculatorProvider>(context);
settingsProvider = Provider.of<SettingsProvider>(context); settingsProvider = Provider.of<SettingsProvider>(context);
getGrades(); getGrades();
final currentAvg = GoalPlannerHelper.averageEvals(grades); final currentAvg = GoalPlannerHelper.averageEvals(grades);
final result = getResult(); final result = getResult();
List<Grade> subjectGrades = getSubjectGrades(widget.subject); List<Grade> subjectGrades = getSubjectGrades(widget.subject);
double avg = AverageHelper.averageEvals(subjectGrades); double avg = AverageHelper.averageEvals(subjectGrades);
var nullavg = GroupAverage(average: 0.0, subject: widget.subject, uid: "0"); var nullavg = GroupAverage(average: 0.0, subject: widget.subject, uid: "0");
double groupAverage = gradeProvider.groupAverages double groupAverage = gradeProvider.groupAverages
.firstWhere((e) => e.subject == widget.subject, orElse: () => nullavg) .firstWhere((e) => e.subject == widget.subject, orElse: () => nullavg)
.average; .average;
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
child: ListView( child: ListView(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 5.0, top: 5.0,
bottom: 220.0, bottom: 220.0,
right: 15.0, right: 15.0,
left: 2.0, left: 2.0,
), ),
children: [ children: [
// Row( // Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [ // children: [
// const BackButton(), // const BackButton(),
// Padding( // Padding(
// padding: const EdgeInsets.only(right: 15.0), // padding: const EdgeInsets.only(right: 15.0),
// child: Row( // child: Row(
// children: [ // children: [
// Text( // Text(
// 'goal_planner_title'.i18n, // 'goal_planner_title'.i18n,
// style: const TextStyle( // style: const TextStyle(
// fontWeight: FontWeight.w500, fontSize: 18.0), // fontWeight: FontWeight.w500, fontSize: 18.0),
// ), // ),
// const SizedBox( // const SizedBox(
// width: 5, // width: 5,
// ), // ),
// const BetaChip(), // const BetaChip(),
// ], // ],
// ), // ),
// ), // ),
// ], // ],
// ), // ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( Row(
children: [ children: [
const BackButton(), const BackButton(),
BackButton( BackButton(
color: Colors.red, color: Colors.red,
onPressed: () => onPressed: () =>
GoalTrackPopup.show(context, subject: widget.subject), GoalTrackPopup.show(context, subject: widget.subject),
), ),
RoundBorderIcon( RoundBorderIcon(
icon: Icon( icon: Icon(
SubjectIcon.resolveVariant( SubjectIcon.resolveVariant(
context: context, context: context,
subject: widget.subject, subject: widget.subject,
), ),
size: 18, size: 18,
weight: 1.5, weight: 1.5,
), ),
), ),
const SizedBox( const SizedBox(
width: 5.0, width: 5.0,
), ),
Text( Text(
(widget.subject.isRenamed (widget.subject.isRenamed
? widget.subject.renamedTo ? widget.subject.renamedTo
: widget.subject.name) ?? : widget.subject.name) ??
'goal_planner_title'.i18n, 'goal_planner_title'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
], ],
), ),
Row( Row(
children: [ children: [
if (groupAverage != 0) if (groupAverage != 0)
AverageDisplay(average: groupAverage, border: true), AverageDisplay(average: groupAverage, border: true),
const SizedBox(width: 6.0), const SizedBox(width: 6.0),
AverageDisplay(average: avg), AverageDisplay(average: avg),
], ],
), ),
], ],
), ),
const SizedBox(height: 12.0), const SizedBox(height: 12.0),
Padding( Padding(
padding: const EdgeInsets.only(left: 22.0, right: 22.0), padding: const EdgeInsets.only(left: 22.0, right: 22.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"set_a_goal".i18n, "set_a_goal".i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 20.0, fontSize: 20.0,
), ),
), ),
const SizedBox(height: 4.0), const SizedBox(height: 4.0),
Text( Text(
goalValue.toString(), goalValue.toString(),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
fontSize: 48.0, fontSize: 48.0,
color: gradeColor(goalValue.round(), settingsProvider), color: gradeColor(goalValue.round(), settingsProvider),
), ),
), ),
// Column( // Column(
// mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
// children: [ // children: [
// Text( // Text(
// "select_subject".i18n, // "select_subject".i18n,
// style: const TextStyle( // style: const TextStyle(
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// fontSize: 20.0, // fontSize: 20.0,
// ), // ),
// ), // ),
// const SizedBox(height: 4.0), // const SizedBox(height: 4.0),
// Column( // Column(
// children: [ // children: [
// Icon( // Icon(
// SubjectIcon.resolveVariant( // SubjectIcon.resolveVariant(
// context: context, // context: context,
// subject: widget.subject, // subject: widget.subject,
// ), // ),
// size: 48.0, // size: 48.0,
// ), // ),
// Text( // Text(
// (widget.subject.isRenamed // (widget.subject.isRenamed
// ? widget.subject.renamedTo // ? widget.subject.renamedTo
// : widget.subject.name) ?? // : widget.subject.name) ??
// '', // '',
// style: const TextStyle( // style: const TextStyle(
// fontSize: 17.0, // fontSize: 17.0,
// fontWeight: FontWeight.w500, // fontWeight: FontWeight.w500,
// ), // ),
// ) // )
// ], // ],
// ) // )
// ], // ],
// ) // )
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
Text( Text(
"pick_route".i18n, "pick_route".i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 20.0, fontSize: 20.0,
), ),
), ),
const SizedBox(height: 12.0), const SizedBox(height: 12.0),
if (recommended != null) if (recommended != null)
RouteOption( RouteOption(
plan: recommended!, plan: recommended!,
mark: RouteMark.recommended, mark: RouteMark.recommended,
selected: selectedRoute == recommended!, selected: selectedRoute == recommended!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = recommended; selectedRoute = recommended;
}), }),
), ),
if (fastest != null && fastest != recommended) if (fastest != null && fastest != recommended)
RouteOption( RouteOption(
plan: fastest!, plan: fastest!,
mark: RouteMark.fastest, mark: RouteMark.fastest,
selected: selectedRoute == fastest!, selected: selectedRoute == fastest!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = fastest; selectedRoute = fastest;
}), }),
), ),
...otherPlans.map((e) => RouteOption( ...otherPlans.map((e) => RouteOption(
plan: e, plan: e,
selected: selectedRoute == e, selected: selectedRoute == e,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = e; selectedRoute = e;
}), }),
)), )),
if (result != PlanResult.available) Text(result.name.i18n), if (result != PlanResult.available) Text(result.name.i18n),
], ],
), ),
), ),
], ],
), ),
), ),
bottomSheet: MediaQuery.removePadding( bottomSheet: MediaQuery.removePadding(
context: context, context: context,
removeBottom: false, removeBottom: false,
removeTop: true, removeTop: true,
child: Container( child: Container(
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
child: Container( child: Container(
padding: const EdgeInsets.only(top: 24.0), padding: const EdgeInsets.only(top: 24.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(24.0)), const BorderRadius.vertical(top: Radius.circular(24.0)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(.1), color: Colors.black.withOpacity(.1),
blurRadius: 8.0, blurRadius: 8.0,
) )
]), ]),
child: SafeArea( child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
GoalInput( GoalInput(
value: goalValue, value: goalValue,
currentAverage: currentAvg, currentAverage: currentAvg,
onChanged: (v) => setState(() { onChanged: (v) => setState(() {
selectedRoute = null; selectedRoute = null;
goalValue = v; goalValue = v;
}), }),
), ),
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: RawMaterialButton( child: RawMaterialButton(
onPressed: () async { onPressed: () async {
if (selectedRoute == null) { if (selectedRoute == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('${"pick_route".i18n}...'))); content: Text('${"pick_route".i18n}...')));
} }
final goalPlans = await fetchGoalPlans(); final goalPlans = await fetchGoalPlans();
final goalAvgs = await fetchGoalAverages(); final goalAvgs = await fetchGoalAverages();
final goalBeforeGrades = await fetchGoalBees(); final goalBeforeGrades = await fetchGoalBees();
final goalPinDates = await fetchGoalPinDates(); final goalPinDates = await fetchGoalPinDates();
goalPlans[widget.subject.id] = goalPlans[widget.subject.id] =
selectedRoute!.dbString; selectedRoute!.dbString;
goalAvgs[widget.subject.id] = goalAvgs[widget.subject.id] =
goalValue.toStringAsFixed(2); goalValue.toStringAsFixed(2);
goalBeforeGrades[widget.subject.id] = goalBeforeGrades[widget.subject.id] =
avg.toStringAsFixed(2); avg.toStringAsFixed(2);
goalPinDates[widget.subject.id] = goalPinDates[widget.subject.id] =
DateTime.now().toIso8601String(); DateTime.now().toIso8601String();
// goalPlans[widget.subject.id] = '1,2,3,4,5,'; // goalPlans[widget.subject.id] = '1,2,3,4,5,';
// goalAvgs[widget.subject.id] = '3.69'; // goalAvgs[widget.subject.id] = '3.69';
// goalBeforeGrades[widget.subject.id] = '3.69'; // goalBeforeGrades[widget.subject.id] = '3.69';
// goalPinDates[widget.subject.id] = // goalPinDates[widget.subject.id] =
// DateTime.now().toIso8601String(); // DateTime.now().toIso8601String();
await dbProvider.userStore.storeSubjectGoalPlans( await dbProvider.userStore.storeSubjectGoalPlans(
goalPlans, goalPlans,
userId: user.id!); userId: user.id!);
await dbProvider.userStore.storeSubjectGoalAverages( await dbProvider.userStore.storeSubjectGoalAverages(
goalAvgs, goalAvgs,
userId: user.id!); userId: user.id!);
await dbProvider.userStore.storeSubjectGoalBefores( await dbProvider.userStore.storeSubjectGoalBefores(
goalBeforeGrades, goalBeforeGrades,
userId: user.id!); userId: user.id!);
await dbProvider.userStore.storeSubjectGoalPinDates( await dbProvider.userStore.storeSubjectGoalPinDates(
goalPinDates, goalPinDates,
userId: user.id!); userId: user.id!);
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
fillColor: Theme.of(context).colorScheme.secondary, fillColor: Theme.of(context).colorScheme.secondary,
shape: const StadiumBorder(), shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
"track_it".i18n, "track_it".i18n,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
), ),
) )
], ],
), ),
), ),
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,70 +1,70 @@
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String { extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
"goal_planner_title": "Goal Planning", "goal_planner_title": "Goal Planning",
"set_a_goal": "Your Goal", "set_a_goal": "Your Goal",
"select_subject": "Subject", "select_subject": "Subject",
"pick_route": "Pick a Route", "pick_route": "Pick a Route",
"track_it": "Track it!", "track_it": "Track it!",
"recommended": "Recommended", "recommended": "Recommended",
"fastest": "Fastest", "fastest": "Fastest",
"unsolvable": "Unsolvable :(", "unsolvable": "Unsolvable :(",
"unreachable": "Unreachable :(", "unreachable": "Unreachable :(",
"reached": "Already reached! :D", "reached": "Already reached! :D",
// new plan popup // new plan popup
"goalplan_title": "Goal Planning", "goalplan_title": "Goal Planning",
"goalplan_subtitle": "Enter the average you want to achieve!", "goalplan_subtitle": "Enter the average you want to achieve!",
"goalplan_plans_title": "Choose the route", "goalplan_plans_title": "Choose the route",
"goalplan_plans_subtitle": "goalplan_plans_subtitle":
"You can achieve your goal with these tickets, choose one that you like the most! You can change this later.", "You can achieve your goal with these tickets, choose one that you like the most! You can change this later.",
"show_my_ways": "Show me my options!", "show_my_ways": "Show me my options!",
}, },
"hu_hu": { "hu_hu": {
"goal_planner_title": "Cél követés", "goal_planner_title": "Cél követés",
"set_a_goal": "Kitűzött cél", "set_a_goal": "Kitűzött cél",
"select_subject": "Tantárgy", "select_subject": "Tantárgy",
"pick_route": "Válassz egy utat", "pick_route": "Válassz egy utat",
"track_it": "Követés!", "track_it": "Követés!",
"recommended": "Ajánlott", "recommended": "Ajánlott",
"fastest": "Leggyorsabb", "fastest": "Leggyorsabb",
"unsolvable": "Megoldhatatlan :(", "unsolvable": "Megoldhatatlan :(",
"unreachable": "Elérhetetlen :(", "unreachable": "Elérhetetlen :(",
"reached": "Már elérted! :D", "reached": "Már elérted! :D",
// new plan popup // new plan popup
"goalplan_title": "Cél kitűzése", "goalplan_title": "Cél kitűzése",
"goalplan_subtitle": "Add meg az elérni kívánt átlagot!", "goalplan_subtitle": "Add meg az elérni kívánt átlagot!",
"goalplan_plans_title": "Válaszd ki az utat", "goalplan_plans_title": "Válaszd ki az utat",
"goalplan_plans_subtitle": "goalplan_plans_subtitle":
"Ezekkel a jegyekkel érheted el a célodat, válassz egyet, ami a legjobban tetszik! Ezt később változtathatod.", "Ezekkel a jegyekkel érheted el a célodat, válassz egyet, ami a legjobban tetszik! Ezt később változtathatod.",
"show_my_ways": "Mutasd a lehetőségeimet!", "show_my_ways": "Mutasd a lehetőségeimet!",
}, },
"de_de": { "de_de": {
"goal_planner_title": "Zielplanung", "goal_planner_title": "Zielplanung",
"set_a_goal": "Dein Ziel", "set_a_goal": "Dein Ziel",
"select_subject": "Thema", "select_subject": "Thema",
"pick_route": "Wähle einen Weg", "pick_route": "Wähle einen Weg",
"track_it": "Verfolge es!", "track_it": "Verfolge es!",
"recommended": "Empfohlen", "recommended": "Empfohlen",
"fastest": "Am schnellsten", "fastest": "Am schnellsten",
"unsolvable": "Unlösbar :(", "unsolvable": "Unlösbar :(",
"unreachable": "Unerreichbar :(", "unreachable": "Unerreichbar :(",
"reached": "Bereits erreicht! :D", "reached": "Bereits erreicht! :D",
// new plan popup // new plan popup
"goalplan_title": "Zielplanung", "goalplan_title": "Zielplanung",
"goalplan_subtitle": "goalplan_subtitle":
"Geben Sie den Durchschnitt ein, den Sie erreichen möchten!", "Geben Sie den Durchschnitt ein, den Sie erreichen möchten!",
"goalplan_plans_title": "Wählen Sie die Route", "goalplan_plans_title": "Wählen Sie die Route",
"goalplan_plans_subtitle": "goalplan_plans_subtitle":
"Sie können Ihr Ziel mit diesen Tickets erreichen. Wählen Sie eines aus, das Ihnen am besten gefällt! Sie können dies später ändern.", "Sie können Ihr Ziel mit diesen Tickets erreichen. Wählen Sie eines aus, das Ihnen am besten gefällt! Sie können dies später ändern.",
"show_my_ways": "Zeigen Sie mir meine Optionen!", "show_my_ways": "Zeigen Sie mir meine Optionen!",
}, },
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params); String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t); String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);
} }

View File

@ -1,471 +1,471 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/average_helper.dart'; import 'package:refilc/helpers/average_helper.dart';
import 'package:refilc/helpers/subject.dart'; import 'package:refilc/helpers/subject.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart'; import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_mobile_ui/common/action_button.dart'; import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_mobile_ui/common/average_display.dart'; import 'package:refilc_mobile_ui/common/average_display.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/progress_bar.dart'; import 'package:refilc_mobile_ui/common/progress_bar.dart';
import 'package:refilc_mobile_ui/common/round_border_icon.dart'; import 'package:refilc_mobile_ui/common/round_border_icon.dart';
import 'package:refilc_plus/providers/goal_provider.dart'; import 'package:refilc_plus/providers/goal_provider.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_state_screen.i18n.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_state_screen.i18n.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_track_popup.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:refilc_plus/ui/mobile/goal_planner/route_option.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'graph.dart'; import 'graph.dart';
class GoalStateScreen extends StatefulWidget { class GoalStateScreen extends StatefulWidget {
final GradeSubject subject; final GradeSubject subject;
const GoalStateScreen({super.key, required this.subject}); const GoalStateScreen({super.key, required this.subject});
@override @override
State<GoalStateScreen> createState() => _GoalStateScreenState(); State<GoalStateScreen> createState() => _GoalStateScreenState();
} }
class _GoalStateScreenState extends State<GoalStateScreen> { class _GoalStateScreenState extends State<GoalStateScreen> {
late UserProvider user; late UserProvider user;
late DatabaseProvider db; late DatabaseProvider db;
late GradeProvider gradeProvider; late GradeProvider gradeProvider;
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
double currAvg = 0.0; double currAvg = 0.0;
double goalAvg = 0.0; double goalAvg = 0.0;
double beforeAvg = 0.0; double beforeAvg = 0.0;
double afterAvg = 0.0; double afterAvg = 0.0;
double avgDifference = 0; double avgDifference = 0;
Plan? plan; Plan? plan;
late Widget gradeGraph; late Widget gradeGraph;
DateTime goalPinDate = DateTime.now(); DateTime goalPinDate = DateTime.now();
void fetchGoalAverages() async { void fetchGoalAverages() async {
var goalAvgRes = await db.userQuery.subjectGoalAverages(userId: user.id!); var goalAvgRes = await db.userQuery.subjectGoalAverages(userId: user.id!);
var beforeAvgRes = await db.userQuery.subjectGoalBefores(userId: user.id!); var beforeAvgRes = await db.userQuery.subjectGoalBefores(userId: user.id!);
goalPinDate = DateTime.parse((await db.userQuery goalPinDate = DateTime.parse((await db.userQuery
.subjectGoalPinDates(userId: user.id!))[widget.subject.id]!); .subjectGoalPinDates(userId: user.id!))[widget.subject.id]!);
String? goalAvgStr = goalAvgRes[widget.subject.id]; String? goalAvgStr = goalAvgRes[widget.subject.id];
String? beforeAvgStr = beforeAvgRes[widget.subject.id]; String? beforeAvgStr = beforeAvgRes[widget.subject.id];
goalAvg = double.parse(goalAvgStr ?? '0.0'); goalAvg = double.parse(goalAvgStr ?? '0.0');
beforeAvg = double.parse(beforeAvgStr ?? '0.0'); beforeAvg = double.parse(beforeAvgStr ?? '0.0');
avgDifference = ((goalAvg - beforeAvg) / beforeAvg.abs()) * 100; avgDifference = ((goalAvg - beforeAvg) / beforeAvg.abs()) * 100;
setState(() {}); setState(() {});
} }
void fetchGoalPlan() async { void fetchGoalPlan() async {
var planRes = await db.userQuery.subjectGoalPlans(userId: user.id!); var planRes = await db.userQuery.subjectGoalPlans(userId: user.id!);
List prePlan = planRes[widget.subject.id]!.split(','); List prePlan = planRes[widget.subject.id]!.split(',');
prePlan.removeLast(); prePlan.removeLast();
plan = Plan( plan = Plan(
prePlan.map((e) => int.parse(e)).toList(), prePlan.map((e) => int.parse(e)).toList(),
); );
setState(() {}); setState(() {});
} }
List<Grade> getSubjectGrades(GradeSubject subject) => List<Grade> getSubjectGrades(GradeSubject subject) =>
gradeProvider.grades.where((e) => (e.subject == subject)).toList(); gradeProvider.grades.where((e) => (e.subject == subject)).toList();
List<Grade> getAfterGoalGrades(GradeSubject subject) => gradeProvider.grades List<Grade> getAfterGoalGrades(GradeSubject subject) => gradeProvider.grades
.where((e) => (e.subject == subject && e.date.isAfter(goalPinDate))) .where((e) => (e.subject == subject && e.date.isAfter(goalPinDate)))
.toList(); .toList();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
user = Provider.of<UserProvider>(context, listen: false); user = Provider.of<UserProvider>(context, listen: false);
db = Provider.of<DatabaseProvider>(context, listen: false); db = Provider.of<DatabaseProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchGoalAverages(); fetchGoalAverages();
fetchGoalPlan(); fetchGoalPlan();
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
gradeProvider = Provider.of<GradeProvider>(context); gradeProvider = Provider.of<GradeProvider>(context);
settingsProvider = Provider.of<SettingsProvider>(context); settingsProvider = Provider.of<SettingsProvider>(context);
var subjectGrades = getSubjectGrades(widget.subject).toList(); var subjectGrades = getSubjectGrades(widget.subject).toList();
currAvg = AverageHelper.averageEvals(subjectGrades); currAvg = AverageHelper.averageEvals(subjectGrades);
var afterGoalGrades = getAfterGoalGrades(widget.subject).toList(); var afterGoalGrades = getAfterGoalGrades(widget.subject).toList();
afterAvg = AverageHelper.averageEvals(afterGoalGrades); afterAvg = AverageHelper.averageEvals(afterGoalGrades);
Color averageColor = currAvg >= 1 && currAvg <= 5 Color averageColor = currAvg >= 1 && currAvg <= 5
? ColorTween( ? ColorTween(
begin: settingsProvider.gradeColors[currAvg.floor() - 1], begin: settingsProvider.gradeColors[currAvg.floor() - 1],
end: settingsProvider.gradeColors[currAvg.ceil() - 1]) end: settingsProvider.gradeColors[currAvg.ceil() - 1])
.transform(currAvg - currAvg.floor())! .transform(currAvg - currAvg.floor())!
: Theme.of(context).colorScheme.secondary; : Theme.of(context).colorScheme.secondary;
gradeGraph = Padding( gradeGraph = Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 12.0, top: 12.0,
bottom: 8.0, bottom: 8.0,
), ),
child: Panel( child: Panel(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: const EdgeInsets.only(top: 16.0, right: 12.0), padding: const EdgeInsets.only(top: 16.0, right: 12.0),
child: GoalGraph(afterGoalGrades, child: GoalGraph(afterGoalGrades,
dayThreshold: 5, classAvg: goalAvg), dayThreshold: 5, classAvg: goalAvg),
), ),
const SizedBox(height: 5.0), const SizedBox(height: 5.0),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'look_at_graph'.i18n, 'look_at_graph'.i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 23.0, fontSize: 23.0,
), ),
), ),
Text( Text(
'thats_progress'.i18n, 'thats_progress'.i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 20.0, fontSize: 20.0,
), ),
), ),
const SizedBox(height: 15.0), const SizedBox(height: 15.0),
ProgressBar( ProgressBar(
value: currAvg / goalAvg, value: currAvg / goalAvg,
backgroundColor: averageColor, backgroundColor: averageColor,
height: 16.0, height: 16.0,
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
], ],
), ),
), ),
], ],
), ),
), ),
); );
return Scaffold( return Scaffold(
body: ListView( body: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: [ children: [
Container( Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
// image: DecorationImage( // image: DecorationImage(
// image: // image:
// AssetImage('assets/images/subject_covers/math_light.png'), // AssetImage('assets/images/subject_covers/math_light.png'),
// fit: BoxFit.fitWidth, // fit: BoxFit.fitWidth,
// alignment: Alignment.topCenter, // alignment: Alignment.topCenter,
// ), // ),
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.2), Theme.of(context).scaffoldBackgroundColor.withOpacity(0.2),
Theme.of(context).scaffoldBackgroundColor, Theme.of(context).scaffoldBackgroundColor,
], ],
stops: const [ stops: const [
0.1, 0.1,
0.22, 0.22,
], ],
), ),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 60.0, top: 60.0,
left: 2.0, left: 2.0,
right: 2.0, right: 2.0,
), ),
child: Column( child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const BackButton(), const BackButton(),
IconButton( IconButton(
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)), borderRadius: BorderRadius.circular(12.0)),
title: Text("attention".i18n), title: Text("attention".i18n),
content: Text("attention_body".i18n), content: Text("attention_body".i18n),
actions: [ actions: [
ActionButton( ActionButton(
label: "delete".i18n, label: "delete".i18n,
onTap: () async { onTap: () async {
// clear the goal // clear the goal
await Provider.of<GoalProvider>(context, await Provider.of<GoalProvider>(context,
listen: false) listen: false)
.clearGoal(widget.subject); .clearGoal(widget.subject);
// close the modal and the goal page // close the modal and the goal page
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
], ],
), ),
); );
}, },
icon: const Icon(FeatherIcons.trash2), icon: const Icon(FeatherIcons.trash2),
), ),
], ],
), ),
const SizedBox(height: 22.0), const SizedBox(height: 22.0),
Column( Column(
children: [ children: [
RoundBorderIcon( RoundBorderIcon(
icon: Icon( icon: Icon(
SubjectIcon.resolveVariant( SubjectIcon.resolveVariant(
context: context, context: context,
subject: widget.subject, subject: widget.subject,
), ),
size: 26.0, size: 26.0,
weight: 2.5, weight: 2.5,
), ),
padding: 8.0, padding: 8.0,
width: 2.5, width: 2.5,
), ),
const SizedBox( const SizedBox(
height: 10.0, height: 10.0,
), ),
Text( Text(
(widget.subject.isRenamed (widget.subject.isRenamed
? widget.subject.renamedTo ? widget.subject.renamedTo
: widget.subject.name) ?? : widget.subject.name) ??
'goal_planner_title'.i18n, 'goal_planner_title'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 30.0, fontSize: 30.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
Text( Text(
'almost_there'.i18n, 'almost_there'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 22.0, fontSize: 22.0,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
height: 1.0, height: 1.0,
), ),
), ),
], ],
), ),
const SizedBox(height: 28.0), const SizedBox(height: 28.0),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0), padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
Text( Text(
'started_with'.i18n, 'started_with'.i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 20.0, fontSize: 20.0,
), ),
), ),
const SizedBox(width: 5.0), const SizedBox(width: 5.0),
AverageDisplay(average: beforeAvg), AverageDisplay(average: beforeAvg),
], ],
), ),
Row( Row(
children: [ children: [
Text( Text(
'current'.i18n, 'current'.i18n,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 20.0, fontSize: 20.0,
), ),
), ),
const SizedBox(width: 5.0), const SizedBox(width: 5.0),
AverageDisplay(average: currAvg), AverageDisplay(average: currAvg),
const SizedBox(width: 5.0), const SizedBox(width: 5.0),
// ide majd kell average difference // ide majd kell average difference
], ],
), ),
], ],
), ),
), ),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Panel( child: Panel(
padding: const EdgeInsets.all(18.0), padding: const EdgeInsets.all(18.0),
child: Column( child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'your_goal'.i18n, 'your_goal'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 23.0, fontSize: 23.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
RawMaterialButton( RawMaterialButton(
onPressed: () async { onPressed: () async {
GoalTrackPopup.show(context, GoalTrackPopup.show(context,
subject: widget.subject); subject: widget.subject);
// Navigator.of(context).push( // Navigator.of(context).push(
// CupertinoPageRoute( // CupertinoPageRoute(
// builder: (context) => // builder: (context) =>
// GoalPlannerScreen( // GoalPlannerScreen(
// subject: widget.subject))); // subject: widget.subject)));
}, },
fillColor: Colors.black, fillColor: Colors.black,
shape: const StadiumBorder(), shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 18.0), horizontal: 18.0),
child: Text( child: Text(
"change_it".i18n, "change_it".i18n,
style: const TextStyle( style: const TextStyle(
height: 1.0, height: 1.0,
color: Colors.white, color: Colors.white,
fontSize: 14.0, fontSize: 14.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
), ),
], ],
), ),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
goalAvg.toString(), goalAvg.toString(),
style: const TextStyle( style: const TextStyle(
height: 1.1, height: 1.1,
fontSize: 42.0, fontSize: 42.0,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
), ),
const SizedBox(width: 10.0), const SizedBox(width: 10.0),
Center( Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 5.0, vertical: 5.0,
horizontal: 8.0, horizontal: 8.0,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45.0), borderRadius: BorderRadius.circular(45.0),
color: avgDifference.isNegative color: avgDifference.isNegative
? Colors.redAccent.shade400 ? Colors.redAccent.shade400
.withOpacity(.15) .withOpacity(.15)
: Colors.greenAccent.shade700 : Colors.greenAccent.shade700
.withOpacity(.15), .withOpacity(.15),
), ),
child: Row( child: Row(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.center, CrossAxisAlignment.center,
children: [ children: [
Icon( Icon(
avgDifference.isNegative avgDifference.isNegative
? FeatherIcons.chevronDown ? FeatherIcons.chevronDown
: FeatherIcons.chevronUp, : FeatherIcons.chevronUp,
color: avgDifference.isNegative color: avgDifference.isNegative
? Colors.redAccent.shade400 ? Colors.redAccent.shade400
: Colors.greenAccent.shade700, : Colors.greenAccent.shade700,
size: 18.0, size: 18.0,
), ),
const SizedBox(width: 5.0), const SizedBox(width: 5.0),
Text( Text(
'${avgDifference.toStringAsFixed(2)}%', '${avgDifference.toStringAsFixed(2)}%',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: avgDifference.isNegative color: avgDifference.isNegative
? Colors.redAccent.shade400 ? Colors.redAccent.shade400
: Colors.greenAccent.shade700, : Colors.greenAccent.shade700,
fontSize: 22.0, fontSize: 22.0,
height: 0.8, height: 0.8,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
], ],
), ),
), ),
), ),
const SizedBox(height: 5.0), const SizedBox(height: 5.0),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: gradeGraph, child: gradeGraph,
), ),
const SizedBox(height: 5.0), const SizedBox(height: 5.0),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 12.0, left: 12.0,
right: 12.0, right: 12.0,
top: 5.0, top: 5.0,
bottom: 8.0, bottom: 8.0,
), ),
child: Panel( child: Panel(
padding: const EdgeInsets.all(18.0), padding: const EdgeInsets.all(18.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'you_need'.i18n, 'you_need'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 23.0, fontSize: 23.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
], ],
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
plan != null plan != null
? RouteOptionRow( ? RouteOptionRow(
plan: plan!, plan: plan!,
) )
: const Text(''), : const Text(''),
], ],
), ),
), ),
), ),
], ],
), ),
), ),
), ),
), ),
], ],
), ),
); );
} }
} }

View File

@ -1,87 +1,87 @@
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String { extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
// base page // base page
"goal_planner_title": "Goal Planning", "goal_planner_title": "Goal Planning",
"almost_there": "Almost there! Keep going!", "almost_there": "Almost there! Keep going!",
"started_with": "Started with:", "started_with": "Started with:",
"current": "Current:", "current": "Current:",
"your_goal": "Your goal:", "your_goal": "Your goal:",
"change_it": "Change it", "change_it": "Change it",
"look_at_graph": "Look at this graph!", "look_at_graph": "Look at this graph!",
"thats_progress": "thats_progress":
"Now that's what I call progress! Push a little more, you're almost there..", "Now that's what I call progress! Push a little more, you're almost there..",
"you_need": "You need:", "you_need": "You need:",
// done modal // done modal
"congrats_title": "🎉 Congratulations!", "congrats_title": "🎉 Congratulations!",
"goal_reached": "You reached your goal after %s days!", "goal_reached": "You reached your goal after %s days!",
"started_at": "You started at", "started_at": "You started at",
"improved_by": "and improved your grade by %s", "improved_by": "and improved your grade by %s",
"detailed_stats": "See my detailed stats", "detailed_stats": "See my detailed stats",
"later": "Yay! I'll see my stats later.", "later": "Yay! I'll see my stats later.",
// sure delete modal // sure delete modal
"delete": "Delete", "delete": "Delete",
"attention": "Attention!", "attention": "Attention!",
"attention_body": "attention_body":
"Your goal and progress will be lost forever and cannot be restored.", "Your goal and progress will be lost forever and cannot be restored.",
}, },
"hu_hu": { "hu_hu": {
// base page // base page
"goal_planner_title": "Cél követés", "goal_planner_title": "Cél követés",
"almost_there": "Majdnem megvan! Így tovább!", "almost_there": "Majdnem megvan! Így tovább!",
"started_with": "Így kezdődött:", "started_with": "Így kezdődött:",
"current": "Jelenlegi:", "current": "Jelenlegi:",
"your_goal": "Célod:", "your_goal": "Célod:",
"change_it": "Megváltoztatás", "change_it": "Megváltoztatás",
"look_at_graph": "Nézd meg ezt a grafikont!", "look_at_graph": "Nézd meg ezt a grafikont!",
"thats_progress": "thats_progress":
"Ezt nevezem haladásnak! Hajts még egy kicsit, már majdnem kész..", "Ezt nevezem haladásnak! Hajts még egy kicsit, már majdnem kész..",
"you_need": "Szükséges:", "you_need": "Szükséges:",
// done modal // done modal
"congrats_title": "🎉 Gratulálunk!", "congrats_title": "🎉 Gratulálunk!",
"goal_reached": "%s nap után érted el a célod!", "goal_reached": "%s nap után érted el a célod!",
"started_at": "Átlagod kezdéskor:", "started_at": "Átlagod kezdéskor:",
"improved_by": "%s-os javulást értél el!", "improved_by": "%s-os javulást értél el!",
"detailed_stats": "Részletes statisztikám", "detailed_stats": "Részletes statisztikám",
"later": "Hurrá! Megnézem máskor.", "later": "Hurrá! Megnézem máskor.",
// sure delete modal // sure delete modal
"delete": "Törlés", "delete": "Törlés",
"attention": "Figyelem!", "attention": "Figyelem!",
"attention_body": "attention_body":
"A kitűzött célod és haladásod örökre elveszik és nem lesz visszaállítható.", "A kitűzött célod és haladásod örökre elveszik és nem lesz visszaállítható.",
}, },
"de_de": { "de_de": {
// base page // base page
"goal_planner_title": "Zielplanung", "goal_planner_title": "Zielplanung",
"almost_there": "Fast dort! Weitermachen!", "almost_there": "Fast dort! Weitermachen!",
"started_with": "Begann mit:", "started_with": "Begann mit:",
"current": "Aktuell:", "current": "Aktuell:",
"your_goal": "Dein Ziel:", "your_goal": "Dein Ziel:",
"change_it": "Ändern Sie es", "change_it": "Ändern Sie es",
"look_at_graph": "Schauen Sie sich diese Grafik an!", "look_at_graph": "Schauen Sie sich diese Grafik an!",
"thats_progress": "thats_progress":
"Das nenne ich Fortschritt! Drücken Sie noch ein wenig, Sie haben es fast geschafft..", "Das nenne ich Fortschritt! Drücken Sie noch ein wenig, Sie haben es fast geschafft..",
"you_need": "Du brauchst:", "you_need": "Du brauchst:",
// done modal // done modal
"congrats_title": "🎉 Glückwunsch!", "congrats_title": "🎉 Glückwunsch!",
"goal_reached": "Du hast dein Ziel nach %s Tagen erreicht!", "goal_reached": "Du hast dein Ziel nach %s Tagen erreicht!",
"started_at": "Gesamtbewertung:", "started_at": "Gesamtbewertung:",
"improved_by": "Sie haben %s Verbesserung erreicht!", "improved_by": "Sie haben %s Verbesserung erreicht!",
"detailed_stats": "Detaillierte Statistiken", "detailed_stats": "Detaillierte Statistiken",
"later": "Hurra! Ich schaue später nach.", "later": "Hurra! Ich schaue später nach.",
// sure delete modal // sure delete modal
"delete": "Löschen", "delete": "Löschen",
"attention": "Achtung!", "attention": "Achtung!",
"attention_body": "attention_body":
"Ihr Ziel und Ihr Fortschritt gehen für immer verloren und können nicht wiederhergestellt werden.", "Ihr Ziel und Ihr Fortschritt gehen für immer verloren und können nicht wiederhergestellt werden.",
}, },
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params); String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t); String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);
} }

View File

@ -1,364 +1,364 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/average_helper.dart'; import 'package:refilc/helpers/average_helper.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/providers/grade_provider.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/average_display.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/plus_provider.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_input.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.i18n.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.i18n.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/route_option.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/route_option.dart';
class GoalTrackPopup extends StatefulWidget { class GoalTrackPopup extends StatefulWidget {
const GoalTrackPopup({super.key, required this.subject}); const GoalTrackPopup({super.key, required this.subject});
final GradeSubject subject; final GradeSubject subject;
static void show(BuildContext context, {required GradeSubject subject}) => static void show(BuildContext context, {required GradeSubject subject}) =>
showRoundedModalBottomSheet( showRoundedModalBottomSheet(
context, context,
child: GoalTrackPopup(subject: subject), child: GoalTrackPopup(subject: subject),
showHandle: true, showHandle: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
); );
@override @override
GoalTrackPopupState createState() => GoalTrackPopupState(); GoalTrackPopupState createState() => GoalTrackPopupState();
} }
class GoalTrackPopupState extends State<GoalTrackPopup> { class GoalTrackPopupState extends State<GoalTrackPopup> {
late UserProvider user; late UserProvider user;
late DatabaseProvider dbProvider; late DatabaseProvider dbProvider;
late GradeProvider gradeProvider; late GradeProvider gradeProvider;
List<Grade> getSubjectGrades(GradeSubject subject) => List<Grade> getSubjectGrades(GradeSubject subject) =>
gradeProvider.grades.where((e) => e.subject == subject).toList(); gradeProvider.grades.where((e) => e.subject == subject).toList();
double goalValue = 4.0; double goalValue = 4.0;
List<Grade> grades = []; List<Grade> grades = [];
Plan? recommended; Plan? recommended;
Plan? fastest; Plan? fastest;
Plan? selectedRoute; Plan? selectedRoute;
List<Plan> otherPlans = []; List<Plan> otherPlans = [];
bool plansPage = false; bool plansPage = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
user = Provider.of<UserProvider>(context, listen: false); user = Provider.of<UserProvider>(context, listen: false);
dbProvider = Provider.of<DatabaseProvider>(context, listen: false); dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
} }
Future<Map<String, String>> fetchGoalPlans() async { Future<Map<String, String>> fetchGoalPlans() async {
return await dbProvider.userQuery.subjectGoalPlans(userId: user.id!); return await dbProvider.userQuery.subjectGoalPlans(userId: user.id!);
} }
Future<Map<String, String>> fetchGoalAverages() async { Future<Map<String, String>> fetchGoalAverages() async {
return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!); return await dbProvider.userQuery.subjectGoalAverages(userId: user.id!);
} }
// haha bees lol // haha bees lol
Future<Map<String, String>> fetchGoalBees() async { Future<Map<String, String>> fetchGoalBees() async {
return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!); return await dbProvider.userQuery.subjectGoalBefores(userId: user.id!);
} }
Future<Map<String, String>> fetchGoalPinDates() async { Future<Map<String, String>> fetchGoalPinDates() async {
return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!); return await dbProvider.userQuery.subjectGoalPinDates(userId: user.id!);
} }
PlanResult getResult() { PlanResult getResult() {
final currentAvg = GoalPlannerHelper.averageEvals(grades); final currentAvg = GoalPlannerHelper.averageEvals(grades);
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
if (currentAvg >= goalValue) return PlanResult.reached; if (currentAvg >= goalValue) return PlanResult.reached;
final planner = GoalPlanner(goalValue, grades); final planner = GoalPlanner(goalValue, grades);
final plans = planner.solve(); final plans = planner.solve();
plans.sort((a, b) => (a.avg - (2 * goalValue + 5) / 3) plans.sort((a, b) => (a.avg - (2 * goalValue + 5) / 3)
.abs() .abs()
.compareTo(b.avg - (2 * goalValue + 5) / 3)); .compareTo(b.avg - (2 * goalValue + 5) / 3));
try { try {
final singleSolution = plans.every((e) => e.sigma == 0); final singleSolution = plans.every((e) => e.sigma == 0);
recommended = recommended =
plans.where((e) => singleSolution ? true : e.sigma > 0).first; plans.where((e) => singleSolution ? true : e.sigma > 0).first;
plans.removeWhere((e) => e == recommended); plans.removeWhere((e) => e == recommended);
} catch (_) {} } catch (_) {}
plans.sort((a, b) => a.plan.length.compareTo(b.plan.length)); plans.sort((a, b) => a.plan.length.compareTo(b.plan.length));
try { try {
fastest = plans.removeAt(0); fastest = plans.removeAt(0);
} catch (_) {} } catch (_) {}
// print((recommended?.plan.length ?? 0).toString() + '-kuki'); // print((recommended?.plan.length ?? 0).toString() + '-kuki');
// print((fastest?.plan.length ?? 0).toString() + '--asd'); // print((fastest?.plan.length ?? 0).toString() + '--asd');
if ((((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0)) >= if ((((recommended?.plan.length ?? 0) - (fastest?.plan.length ?? 0)) >=
5) && 5) &&
fastest != null) { fastest != null) {
recommended = fastest; recommended = fastest;
} }
if (recommended == null) { if (recommended == null) {
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
selectedRoute = null; selectedRoute = null;
return PlanResult.unsolvable; return PlanResult.unsolvable;
} }
// print(recommended!.plan.length.toString() + '--------'); // print(recommended!.plan.length.toString() + '--------');
if (recommended!.plan.length > 20) { if (recommended!.plan.length > 20) {
recommended = null; recommended = null;
fastest = null; fastest = null;
otherPlans = []; otherPlans = [];
selectedRoute = null; selectedRoute = null;
return PlanResult.unreachable; return PlanResult.unreachable;
} }
otherPlans = List.from(plans); otherPlans = List.from(plans);
// only save 2 items if not plus member // only save 2 items if not plus member
if (!Provider.of<PlusProvider>(context) if (!Provider.of<PlusProvider>(context)
.hasScope(PremiumScopes.unlimitedGoalPlanner)) { .hasScope(PremiumScopes.unlimitedGoalPlanner)) {
if (otherPlans.length > 2) { if (otherPlans.length > 2) {
otherPlans.removeRange(2, otherPlans.length - 1); otherPlans.removeRange(2, otherPlans.length - 1);
} }
} }
return PlanResult.available; return PlanResult.available;
} }
void getGrades() { void getGrades() {
grades = getSubjectGrades(widget.subject).toList(); grades = getSubjectGrades(widget.subject).toList();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
gradeProvider = Provider.of<GradeProvider>(context); gradeProvider = Provider.of<GradeProvider>(context);
getGrades(); getGrades();
final currentAvg = GoalPlannerHelper.averageEvals(grades); final currentAvg = GoalPlannerHelper.averageEvals(grades);
final result = getResult(); final result = getResult();
List<Grade> subjectGrades = getSubjectGrades(widget.subject); List<Grade> subjectGrades = getSubjectGrades(widget.subject);
double avg = AverageHelper.averageEvals(subjectGrades); double avg = AverageHelper.averageEvals(subjectGrades);
double listLength = (otherPlans.length + double listLength = (otherPlans.length +
(recommended != null ? 1 : 0) + (recommended != null ? 1 : 0) +
(fastest != null && fastest != recommended ? 1 : 0)); (fastest != null && fastest != recommended ? 1 : 0));
return Container( return Container(
padding: const EdgeInsets.only(top: 24.0), padding: const EdgeInsets.only(top: 24.0),
child: SafeArea( child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Column( Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
AverageDisplay( AverageDisplay(
average: avg, average: avg,
scale: 1.3, scale: 1.3,
), ),
const SizedBox(width: 12.0), const SizedBox(width: 12.0),
const Icon( const Icon(
Icons.arrow_forward, Icons.arrow_forward,
size: 24.0, size: 24.0,
), ),
const SizedBox(width: 12.0), const SizedBox(width: 12.0),
AverageDisplay( AverageDisplay(
average: goalValue, average: goalValue,
border: true, border: true,
dashed: true, dashed: true,
scale: 1.3, scale: 1.3,
), ),
], ],
), ),
const SizedBox( const SizedBox(
height: 14.0, height: 14.0,
), ),
Text( Text(
plansPage plansPage
? 'goalplan_plans_title'.i18n ? 'goalplan_plans_title'.i18n
: 'goalplan_title'.i18n, : 'goalplan_title'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 20.0, fontWeight: FontWeight.w700), fontSize: 20.0, fontWeight: FontWeight.w700),
textAlign: TextAlign.center), textAlign: TextAlign.center),
Text( Text(
plansPage plansPage
? 'goalplan_plans_subtitle'.i18n ? 'goalplan_plans_subtitle'.i18n
: 'goalplan_subtitle'.i18n, : 'goalplan_subtitle'.i18n,
style: const TextStyle( style: const TextStyle(
fontSize: 16.0, fontWeight: FontWeight.w500), fontSize: 16.0, fontWeight: FontWeight.w500),
textAlign: TextAlign.center), textAlign: TextAlign.center),
], ],
), ),
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
if (!plansPage) if (!plansPage)
GoalInput( GoalInput(
value: goalValue, value: goalValue,
currentAverage: currentAvg, currentAverage: currentAvg,
onChanged: (v) => setState(() { onChanged: (v) => setState(() {
selectedRoute = null; selectedRoute = null;
goalValue = v; goalValue = v;
}), }),
), ),
if (plansPage && listLength > 2) if (plansPage && listLength > 2)
SizedBox( SizedBox(
height: (MediaQuery.of(context).size.height * 0.5), height: (MediaQuery.of(context).size.height * 0.5),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
if (recommended != null) if (recommended != null)
RouteOption( RouteOption(
plan: recommended!, plan: recommended!,
mark: RouteMark.recommended, mark: RouteMark.recommended,
selected: selectedRoute == recommended!, selected: selectedRoute == recommended!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = recommended; selectedRoute = recommended;
}), }),
), ),
if (fastest != null && fastest != recommended) if (fastest != null && fastest != recommended)
RouteOption( RouteOption(
plan: fastest!, plan: fastest!,
mark: RouteMark.fastest, mark: RouteMark.fastest,
selected: selectedRoute == fastest!, selected: selectedRoute == fastest!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = fastest; selectedRoute = fastest;
}), }),
), ),
...otherPlans.map((e) => RouteOption( ...otherPlans.map((e) => RouteOption(
plan: e, plan: e,
selected: selectedRoute == e, selected: selectedRoute == e,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = e; selectedRoute = e;
}), }),
)), )),
if (result != PlanResult.available) if (result != PlanResult.available)
Text(result.name.i18n), Text(result.name.i18n),
], ],
), ),
), ),
), ),
if (plansPage && listLength <= 2) if (plansPage && listLength <= 2)
Column( Column(
children: [ children: [
if (recommended != null) if (recommended != null)
RouteOption( RouteOption(
plan: recommended!, plan: recommended!,
mark: RouteMark.recommended, mark: RouteMark.recommended,
selected: selectedRoute == recommended!, selected: selectedRoute == recommended!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = recommended; selectedRoute = recommended;
}), }),
), ),
if (fastest != null && fastest != recommended) if (fastest != null && fastest != recommended)
RouteOption( RouteOption(
plan: fastest!, plan: fastest!,
mark: RouteMark.fastest, mark: RouteMark.fastest,
selected: selectedRoute == fastest!, selected: selectedRoute == fastest!,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = fastest; selectedRoute = fastest;
}), }),
), ),
...otherPlans.map((e) => RouteOption( ...otherPlans.map((e) => RouteOption(
plan: e, plan: e,
selected: selectedRoute == e, selected: selectedRoute == e,
onSelected: () => setState(() { onSelected: () => setState(() {
selectedRoute = e; selectedRoute = e;
}), }),
)), )),
if (result != PlanResult.available) Text(result.name.i18n), if (result != PlanResult.available) Text(result.name.i18n),
], ],
), ),
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: RawMaterialButton( child: RawMaterialButton(
onPressed: () async { onPressed: () async {
if (!plansPage) { if (!plansPage) {
setState(() { setState(() {
plansPage = true; plansPage = true;
}); });
return; return;
} }
if (selectedRoute == null) { if (selectedRoute == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${"pick_route".i18n}...'))); SnackBar(content: Text('${"pick_route".i18n}...')));
} }
final goalPlans = await fetchGoalPlans(); final goalPlans = await fetchGoalPlans();
final goalAvgs = await fetchGoalAverages(); final goalAvgs = await fetchGoalAverages();
final goalBeforeGrades = await fetchGoalBees(); final goalBeforeGrades = await fetchGoalBees();
final goalPinDates = await fetchGoalPinDates(); final goalPinDates = await fetchGoalPinDates();
goalPlans[widget.subject.id] = selectedRoute!.dbString; goalPlans[widget.subject.id] = selectedRoute!.dbString;
goalAvgs[widget.subject.id] = goalValue.toStringAsFixed(2); goalAvgs[widget.subject.id] = goalValue.toStringAsFixed(2);
goalBeforeGrades[widget.subject.id] = goalBeforeGrades[widget.subject.id] =
avg.toStringAsFixed(2); avg.toStringAsFixed(2);
goalPinDates[widget.subject.id] = goalPinDates[widget.subject.id] =
DateTime.now().toIso8601String(); DateTime.now().toIso8601String();
// goalPlans[widget.subject.id] = '1,2,3,4,5,'; // goalPlans[widget.subject.id] = '1,2,3,4,5,';
// goalAvgs[widget.subject.id] = '3.69'; // goalAvgs[widget.subject.id] = '3.69';
// goalBeforeGrades[widget.subject.id] = '3.69'; // goalBeforeGrades[widget.subject.id] = '3.69';
// goalPinDates[widget.subject.id] = // goalPinDates[widget.subject.id] =
// DateTime.now().toIso8601String(); // DateTime.now().toIso8601String();
await dbProvider.userStore await dbProvider.userStore
.storeSubjectGoalPlans(goalPlans, userId: user.id!); .storeSubjectGoalPlans(goalPlans, userId: user.id!);
await dbProvider.userStore await dbProvider.userStore
.storeSubjectGoalAverages(goalAvgs, userId: user.id!); .storeSubjectGoalAverages(goalAvgs, userId: user.id!);
await dbProvider.userStore.storeSubjectGoalBefores( await dbProvider.userStore.storeSubjectGoalBefores(
goalBeforeGrades, goalBeforeGrades,
userId: user.id!); userId: user.id!);
await dbProvider.userStore.storeSubjectGoalPinDates( await dbProvider.userStore.storeSubjectGoalPinDates(
goalPinDates, goalPinDates,
userId: user.id!); userId: user.id!);
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
fillColor: Theme.of(context).colorScheme.secondary, fillColor: Theme.of(context).colorScheme.secondary,
shape: const StadiumBorder(), shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
plansPage ? "track_it".i18n : "show_my_ways".i18n, plansPage ? "track_it".i18n : "show_my_ways".i18n,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
), ),
) )
], ],
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,34 +1,34 @@
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_input.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_input.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class GradeDisplay extends StatelessWidget { class GradeDisplay extends StatelessWidget {
const GradeDisplay({super.key, required this.grade}); const GradeDisplay({super.key, required this.grade});
final int grade; final int grade;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SettingsProvider settings = Provider.of<SettingsProvider>(context); SettingsProvider settings = Provider.of<SettingsProvider>(context);
return Container( return Container(
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: gradeColor(grade, settings).withOpacity(.3), color: gradeColor(grade, settings).withOpacity(.3),
), ),
child: Center( child: Center(
child: Text( child: Text(
grade.toInt().toString(), grade.toInt().toString(),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 22.0, fontSize: 22.0,
color: gradeColor(grade, settings), color: gradeColor(grade, settings),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,269 +1,269 @@
import 'dart:math'; import 'dart:math';
import 'package:refilc/helpers/average_helper.dart'; import 'package:refilc/helpers/average_helper.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/graph.i18n.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/graph.i18n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class GoalGraph extends StatefulWidget { class GoalGraph extends StatefulWidget {
const GoalGraph(this.data, {super.key, this.dayThreshold = 7, this.classAvg}); const GoalGraph(this.data, {super.key, this.dayThreshold = 7, this.classAvg});
final List<Grade> data; final List<Grade> data;
final int dayThreshold; final int dayThreshold;
final double? classAvg; final double? classAvg;
@override @override
GoalGraphState createState() => GoalGraphState(); GoalGraphState createState() => GoalGraphState();
} }
class GoalGraphState extends State<GoalGraph> { class GoalGraphState extends State<GoalGraph> {
late SettingsProvider settings; late SettingsProvider settings;
List<FlSpot> getSpots(List<Grade> data) { List<FlSpot> getSpots(List<Grade> data) {
List<FlSpot> subjectData = []; List<FlSpot> subjectData = [];
List<List<Grade>> sortedData = [[]]; List<List<Grade>> sortedData = [[]];
// Sort by date descending // Sort by date descending
data.sort((a, b) => -a.writeDate.compareTo(b.writeDate)); data.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
// Sort data to points by treshold // Sort data to points by treshold
for (var element in data) { for (var element in data) {
if (sortedData.last.isNotEmpty && if (sortedData.last.isNotEmpty &&
sortedData.last.last.writeDate.difference(element.writeDate).inDays > sortedData.last.last.writeDate.difference(element.writeDate).inDays >
widget.dayThreshold) { widget.dayThreshold) {
sortedData.add([]); sortedData.add([]);
} }
for (var dataList in sortedData) { for (var dataList in sortedData) {
dataList.add(element); dataList.add(element);
} }
} }
// Create FlSpots from points // Create FlSpots from points
for (var dataList in sortedData) { for (var dataList in sortedData) {
double average = AverageHelper.averageEvals(dataList); double average = AverageHelper.averageEvals(dataList);
if (dataList.isNotEmpty) { if (dataList.isNotEmpty) {
subjectData.add(FlSpot( subjectData.add(FlSpot(
dataList[0].writeDate.month + dataList[0].writeDate.month +
(dataList[0].writeDate.day / 31) + (dataList[0].writeDate.day / 31) +
((dataList[0].writeDate.year - data.last.writeDate.year) * 12), ((dataList[0].writeDate.year - data.last.writeDate.year) * 12),
double.parse(average.toStringAsFixed(2)), double.parse(average.toStringAsFixed(2)),
)); ));
} }
} }
return subjectData; return subjectData;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
settings = Provider.of<SettingsProvider>(context); settings = Provider.of<SettingsProvider>(context);
List<FlSpot> subjectSpots = []; List<FlSpot> subjectSpots = [];
List<FlSpot> ghostSpots = []; List<FlSpot> ghostSpots = [];
List<VerticalLine> extraLinesV = []; List<VerticalLine> extraLinesV = [];
List<HorizontalLine> extraLinesH = []; List<HorizontalLine> extraLinesH = [];
// Filter data // Filter data
List<Grade> data = widget.data List<Grade> data = widget.data
.where((e) => e.value.weight != 0) .where((e) => e.value.weight != 0)
.where((e) => e.type == GradeType.midYear) .where((e) => e.type == GradeType.midYear)
.where((e) => e.gradeType?.name == "Osztalyzat") .where((e) => e.gradeType?.name == "Osztalyzat")
.toList(); .toList();
// Filter ghost data // Filter ghost data
List<Grade> ghostData = widget.data List<Grade> ghostData = widget.data
.where((e) => e.value.weight != 0) .where((e) => e.value.weight != 0)
.where((e) => e.type == GradeType.ghost) .where((e) => e.type == GradeType.ghost)
.toList(); .toList();
// Calculate average // Calculate average
double average = AverageHelper.averageEvals(data); double average = AverageHelper.averageEvals(data);
// Calculate graph color // Calculate graph color
Color averageColor = average >= 1 && average <= 5 Color averageColor = average >= 1 && average <= 5
? ColorTween( ? ColorTween(
begin: settings.gradeColors[average.floor() - 1], begin: settings.gradeColors[average.floor() - 1],
end: settings.gradeColors[average.ceil() - 1]) end: settings.gradeColors[average.ceil() - 1])
.transform(average - average.floor())! .transform(average - average.floor())!
: Theme.of(context).colorScheme.secondary; : Theme.of(context).colorScheme.secondary;
subjectSpots = getSpots(data); subjectSpots = getSpots(data);
// naplo/#73 // naplo/#73
if (subjectSpots.isNotEmpty) { if (subjectSpots.isNotEmpty) {
ghostSpots = getSpots(data + ghostData); ghostSpots = getSpots(data + ghostData);
// hax // hax
ghostSpots = ghostSpots ghostSpots = ghostSpots
.where((e) => e.x >= subjectSpots.map((f) => f.x).reduce(max)) .where((e) => e.x >= subjectSpots.map((f) => f.x).reduce(max))
.toList(); .toList();
ghostSpots = ghostSpots.map((e) => FlSpot(e.x + 0.1, e.y)).toList(); ghostSpots = ghostSpots.map((e) => FlSpot(e.x + 0.1, e.y)).toList();
ghostSpots.add(subjectSpots.firstWhere( ghostSpots.add(subjectSpots.firstWhere(
(e) => e.x >= subjectSpots.map((f) => f.x).reduce(max), (e) => e.x >= subjectSpots.map((f) => f.x).reduce(max),
orElse: () => const FlSpot(-1, -1))); orElse: () => const FlSpot(-1, -1)));
ghostSpots.removeWhere( ghostSpots.removeWhere(
(element) => element.x == -1 && element.y == -1); // naplo/#74 (element) => element.x == -1 && element.y == -1); // naplo/#74
} }
// Horizontal line displaying the class average // Horizontal line displaying the class average
if (widget.classAvg != null && if (widget.classAvg != null &&
widget.classAvg! > 0.0 && widget.classAvg! > 0.0 &&
settings.graphClassAvg) { settings.graphClassAvg) {
extraLinesH.add(HorizontalLine( extraLinesH.add(HorizontalLine(
y: widget.classAvg!, y: widget.classAvg!,
color: AppColors.of(context).text.withOpacity(.75), color: AppColors.of(context).text.withOpacity(.75),
)); ));
} }
// LineChart is really cute because it tries to render it's contents outside of it's rect. // LineChart is really cute because it tries to render it's contents outside of it's rect.
return widget.data.length <= 2 return widget.data.length <= 2
? SizedBox( ? SizedBox(
height: 150, height: 150,
child: Center( child: Center(
child: Text( child: Text(
"not_enough_grades".i18n, "not_enough_grades".i18n,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
) )
: ClipRect( : ClipRect(
child: SizedBox( child: SizedBox(
height: 158, height: 158,
child: subjectSpots.length > 1 child: subjectSpots.length > 1
? Padding( ? Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8.0), padding: const EdgeInsets.only(top: 8.0, right: 8.0),
child: LineChart( child: LineChart(
LineChartData( LineChartData(
extraLinesData: ExtraLinesData( extraLinesData: ExtraLinesData(
verticalLines: extraLinesV, verticalLines: extraLinesV,
horizontalLines: extraLinesH), horizontalLines: extraLinesH),
lineBarsData: [ lineBarsData: [
LineChartBarData( LineChartBarData(
preventCurveOverShooting: true, preventCurveOverShooting: true,
spots: subjectSpots, spots: subjectSpots,
isCurved: true, isCurved: true,
color: averageColor, color: averageColor,
barWidth: 8, barWidth: 8,
isStrokeCapRound: true, isStrokeCapRound: true,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
averageColor.withOpacity(0.7), averageColor.withOpacity(0.7),
averageColor.withOpacity(0.3), averageColor.withOpacity(0.3),
averageColor.withOpacity(0.2), averageColor.withOpacity(0.2),
averageColor.withOpacity(0.1), averageColor.withOpacity(0.1),
], ],
stops: const [0.1, 0.6, 0.8, 1], stops: const [0.1, 0.6, 0.8, 1],
), ),
// colors: [ // colors: [
// averageColor.withOpacity(0.7), // averageColor.withOpacity(0.7),
// averageColor.withOpacity(0.3), // averageColor.withOpacity(0.3),
// averageColor.withOpacity(0.2), // averageColor.withOpacity(0.2),
// averageColor.withOpacity(0.1), // averageColor.withOpacity(0.1),
// ], // ],
// gradientColorStops: [0.1, 0.6, 0.8, 1], // gradientColorStops: [0.1, 0.6, 0.8, 1],
// gradientFrom: const Offset(0, 0), // gradientFrom: const Offset(0, 0),
// gradientTo: const Offset(0, 1), // gradientTo: const Offset(0, 1),
), ),
), ),
if (ghostData.isNotEmpty && ghostSpots.isNotEmpty) if (ghostData.isNotEmpty && ghostSpots.isNotEmpty)
LineChartBarData( LineChartBarData(
preventCurveOverShooting: true, preventCurveOverShooting: true,
spots: ghostSpots, spots: ghostSpots,
isCurved: true, isCurved: true,
color: AppColors.of(context).text, color: AppColors.of(context).text,
barWidth: 8, barWidth: 8,
isStrokeCapRound: true, isStrokeCapRound: true,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
AppColors.of(context) AppColors.of(context)
.text .text
.withOpacity(0.7), .withOpacity(0.7),
AppColors.of(context) AppColors.of(context)
.text .text
.withOpacity(0.3), .withOpacity(0.3),
AppColors.of(context) AppColors.of(context)
.text .text
.withOpacity(0.2), .withOpacity(0.2),
AppColors.of(context) AppColors.of(context)
.text .text
.withOpacity(0.1), .withOpacity(0.1),
], ],
stops: const [0.1, 0.6, 0.8, 1], stops: const [0.1, 0.6, 0.8, 1],
), ),
), ),
), ),
], ],
minY: 1, minY: 1,
maxY: 5, maxY: 5,
gridData: const FlGridData( gridData: const FlGridData(
show: true, show: true,
horizontalInterval: 1, horizontalInterval: 1,
// checkToShowVerticalLine: (_) => false, // checkToShowVerticalLine: (_) => false,
// getDrawingHorizontalLine: (_) => FlLine( // getDrawingHorizontalLine: (_) => FlLine(
// color: AppColors.of(context).text.withOpacity(.15), // color: AppColors.of(context).text.withOpacity(.15),
// strokeWidth: 2, // strokeWidth: 2,
// ), // ),
// getDrawingVerticalLine: (_) => FlLine( // getDrawingVerticalLine: (_) => FlLine(
// color: AppColors.of(context).text.withOpacity(.25), // color: AppColors.of(context).text.withOpacity(.25),
// strokeWidth: 2, // strokeWidth: 2,
// ), // ),
), ),
lineTouchData: LineTouchData( lineTouchData: LineTouchData(
touchTooltipData: const LineTouchTooltipData( touchTooltipData: const LineTouchTooltipData(
// tooltipBgColor: Colors.grey.shade800, // tooltipBgColor: Colors.grey.shade800,
fitInsideVertically: true, fitInsideVertically: true,
fitInsideHorizontally: true, fitInsideHorizontally: true,
), ),
handleBuiltInTouches: true, handleBuiltInTouches: true,
touchSpotThreshold: 20.0, touchSpotThreshold: 20.0,
getTouchedSpotIndicator: (_, spots) { getTouchedSpotIndicator: (_, spots) {
return List.generate( return List.generate(
spots.length, spots.length,
(index) => TouchedSpotIndicatorData( (index) => TouchedSpotIndicatorData(
FlLine( FlLine(
color: Colors.grey.shade900, color: Colors.grey.shade900,
strokeWidth: 3.5, strokeWidth: 3.5,
), ),
FlDotData( FlDotData(
getDotPainter: (a, b, c, d) => getDotPainter: (a, b, c, d) =>
FlDotCirclePainter( FlDotCirclePainter(
strokeWidth: 0, strokeWidth: 0,
color: Colors.grey.shade900, color: Colors.grey.shade900,
radius: 10.0, radius: 10.0,
), ),
), ),
), ),
); );
}, },
), ),
borderData: FlBorderData( borderData: FlBorderData(
show: false, show: false,
border: Border.all( border: Border.all(
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
width: 4, width: 4,
), ),
), ),
), ),
), ),
) )
: null, : null,
), ),
); );
} }
} }

View File

@ -1,21 +1,21 @@
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String { extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
"not_enough_grades": "Not enough data to show a graph here.", "not_enough_grades": "Not enough data to show a graph here.",
}, },
"hu_hu": { "hu_hu": {
"not_enough_grades": "Nem szereztél még elég jegyet grafikon mutatáshoz.", "not_enough_grades": "Nem szereztél még elég jegyet grafikon mutatáshoz.",
}, },
"de_de": { "de_de": {
"not_enough_grades": "Noch nicht genug Noten, um die Grafik zu zeigen.", "not_enough_grades": "Noch nicht genug Noten, um die Grafik zu zeigen.",
}, },
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params); String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t); String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);
} }

View File

@ -1,204 +1,204 @@
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner.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_planner_screen.i18n.dart';
import 'package:refilc_plus/ui/mobile/goal_planner/grade_display.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/grade_display.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum RouteMark { recommended, fastest } enum RouteMark { recommended, fastest }
class RouteOption extends StatelessWidget { class RouteOption extends StatelessWidget {
const RouteOption( const RouteOption(
{super.key, {super.key,
required this.plan, required this.plan,
this.mark, this.mark,
this.selected = false, this.selected = false,
required this.onSelected}); required this.onSelected});
final Plan plan; final Plan plan;
final RouteMark? mark; final RouteMark? mark;
final bool selected; final bool selected;
final void Function() onSelected; final void Function() onSelected;
Widget markLabel({Color? colorOverride}) { Widget markLabel({Color? colorOverride}) {
TextStyle style = TextStyle style =
TextStyle(fontWeight: FontWeight.bold, color: colorOverride); TextStyle(fontWeight: FontWeight.bold, color: colorOverride);
switch (mark!) { switch (mark!) {
case RouteMark.recommended: case RouteMark.recommended:
return Text("recommended".i18n, style: style); return Text("recommended".i18n, style: style);
case RouteMark.fastest: case RouteMark.fastest:
return Text("fastest".i18n, style: style); return Text("fastest".i18n, style: style);
} }
} }
Color markColor(BuildContext context) { Color markColor(BuildContext context) {
switch (mark) { switch (mark) {
case RouteMark.recommended: case RouteMark.recommended:
return const Color.fromARGB(255, 104, 93, 255); return const Color.fromARGB(255, 104, 93, 255);
case RouteMark.fastest: case RouteMark.fastest:
return const Color.fromARGB(255, 255, 91, 146); return const Color.fromARGB(255, 255, 91, 146);
default: default:
return Theme.of(context).colorScheme.primary; return Theme.of(context).colorScheme.primary;
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> gradeWidgets = []; List<Widget> gradeWidgets = [];
for (int i = 5; i > 1; i--) { for (int i = 5; i > 1; i--) {
final count = plan.plan.where((e) => e == i).length; final count = plan.plan.where((e) => e == i).length;
if (count > 4) { if (count > 4) {
gradeWidgets.add(Row( gradeWidgets.add(Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"${count}x", "${count}x",
style: TextStyle( style: TextStyle(
fontSize: 22.0, fontSize: 22.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.7), color: AppColors.of(context).text.withOpacity(.7),
), ),
), ),
const SizedBox(width: 4.0), const SizedBox(width: 4.0),
GradeDisplay(grade: i), GradeDisplay(grade: i),
], ],
)); ));
} else { } else {
gradeWidgets gradeWidgets
.addAll(List.generate(count, (_) => GradeDisplay(grade: i))); .addAll(List.generate(count, (_) => GradeDisplay(grade: i)));
} }
if (count > 0) { if (count > 0) {
gradeWidgets.add(SizedBox( gradeWidgets.add(SizedBox(
height: 36.0, height: 36.0,
width: 32.0, width: 32.0,
child: Center( child: Center(
child: Icon(Icons.add, child: Icon(Icons.add,
color: AppColors.of(context).text.withOpacity(.5))), color: AppColors.of(context).text.withOpacity(.5))),
)); ));
} }
} }
gradeWidgets.removeLast(); gradeWidgets.removeLast();
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12.0), padding: const EdgeInsets.only(bottom: 12.0),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: Card( child: Card(
surfaceTintColor: surfaceTintColor:
selected ? markColor(context).withOpacity(.2) : Colors.white, selected ? markColor(context).withOpacity(.2) : Colors.white,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
elevation: 5, elevation: 5,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0), borderRadius: BorderRadius.circular(16.0),
side: selected side: selected
? BorderSide(color: markColor(context), width: 1.5) ? BorderSide(color: markColor(context), width: 1.5)
: BorderSide.none, : BorderSide.none,
), ),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(16.0), borderRadius: BorderRadius.circular(16.0),
onTap: onSelected, onTap: onSelected,
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 16.0, bottom: 16.0, left: 20.0, right: 12.0), top: 16.0, bottom: 16.0, left: 20.0, right: 12.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (mark != null) ...[ if (mark != null) ...[
// Chip( // Chip(
// label: markLabel(), // label: markLabel(),
// visualDensity: VisualDensity.compact, // visualDensity: VisualDensity.compact,
// backgroundColor: // backgroundColor:
// selected ? markColor(context) : Colors.transparent, // selected ? markColor(context) : Colors.transparent,
// labelPadding: const EdgeInsets.symmetric(horizontal: 8.0), // labelPadding: const EdgeInsets.symmetric(horizontal: 8.0),
// labelStyle: // labelStyle:
// TextStyle(color: selected ? Colors.white : null), // TextStyle(color: selected ? Colors.white : null),
// shape: StadiumBorder( // shape: StadiumBorder(
// side: BorderSide( // side: BorderSide(
// color: markColor(context), // color: markColor(context),
// width: 3.0, // width: 3.0,
// ), // ),
// ), // ),
// ), // ),
markLabel( markLabel(
colorOverride: selected ? markColor(context) : null), colorOverride: selected ? markColor(context) : null),
const SizedBox(height: 6.0), const SizedBox(height: 6.0),
], ],
Wrap( Wrap(
spacing: 4.0, spacing: 4.0,
runSpacing: 8.0, runSpacing: 8.0,
children: gradeWidgets, children: gradeWidgets,
), ),
], ],
), ),
), ),
), ),
), ),
), ),
); );
} }
} }
class RouteOptionRow extends StatelessWidget { class RouteOptionRow extends StatelessWidget {
const RouteOptionRow({ const RouteOptionRow({
super.key, super.key,
required this.plan, required this.plan,
this.mark, this.mark,
}); });
final Plan plan; final Plan plan;
final RouteMark? mark; final RouteMark? mark;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> gradeWidgets = []; List<Widget> gradeWidgets = [];
for (int i = 5; i > 1; i--) { for (int i = 5; i > 1; i--) {
final count = plan.plan.where((e) => e == i).length; final count = plan.plan.where((e) => e == i).length;
if (count > 4) { if (count > 4) {
gradeWidgets.add(Row( gradeWidgets.add(Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"${count}x", "${count}x",
style: TextStyle( style: TextStyle(
fontSize: 22.0, fontSize: 22.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.7), color: AppColors.of(context).text.withOpacity(.7),
), ),
), ),
const SizedBox(width: 4.0), const SizedBox(width: 4.0),
GradeDisplay(grade: i), GradeDisplay(grade: i),
], ],
)); ));
} else { } else {
gradeWidgets gradeWidgets
.addAll(List.generate(count, (_) => GradeDisplay(grade: i))); .addAll(List.generate(count, (_) => GradeDisplay(grade: i)));
} }
if (count > 0) { if (count > 0) {
gradeWidgets.add(SizedBox( gradeWidgets.add(SizedBox(
height: 36.0, height: 36.0,
width: 32.0, width: 32.0,
child: Center( child: Center(
child: Icon(Icons.add, child: Icon(Icons.add,
color: AppColors.of(context).text.withOpacity(.5))), color: AppColors.of(context).text.withOpacity(.5))),
)); ));
} }
} }
gradeWidgets.removeLast(); gradeWidgets.removeLast();
return Wrap( return Wrap(
spacing: 4.0, spacing: 4.0,
runSpacing: 8.0, runSpacing: 8.0,
children: gradeWidgets, children: gradeWidgets,
); );
} }
} }

View File

@ -1,201 +1,204 @@
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class ActivationDashboard extends StatefulWidget { class ActivationDashboard extends StatefulWidget {
const ActivationDashboard({super.key}); const ActivationDashboard({super.key});
@override @override
State<ActivationDashboard> createState() => _ActivationDashboardState(); State<ActivationDashboard> createState() => _ActivationDashboardState();
} }
class _ActivationDashboardState extends State<ActivationDashboard> { class _ActivationDashboardState extends State<ActivationDashboard> {
bool manualActivationLoading = false; bool manualActivationLoading = false;
Future<void> onManualActivation() async { Future<void> onManualActivation() async {
final data = await Clipboard.getData("text/plain"); final data = await Clipboard.getData("text/plain");
if (data == null || data.text == null || data.text == "") { if (data == null || data.text == null || data.text == "") {
return; return;
} }
setState(() { setState(() {
manualActivationLoading = true; manualActivationLoading = true;
}); });
final result = final result =
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await context.read<PlusProvider>().auth.finishAuth(data.text!); await context
setState(() { .read<PlusProvider>()
manualActivationLoading = false; .auth
}); .finishAuth(data.text!);
setState(() {
if (!result && mounted) { manualActivationLoading = false;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( });
content: Text(
"Sikertelen aktiválás. Kérlek próbáld újra később!", if (!result && mounted) {
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
), content: Text(
backgroundColor: Colors.red, "Sikertelen aktiválás. Kérlek próbáld újra később!",
)); style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
} ),
} backgroundColor: Colors.red,
));
@override }
Widget build(BuildContext context) { }
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0), @override
child: Column( Widget build(BuildContext context) {
mainAxisAlignment: MainAxisAlignment.center, return Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.symmetric(horizontal: 24.0),
children: [ child: Column(
const Spacer(), mainAxisAlignment: MainAxisAlignment.center,
Center( crossAxisAlignment: CrossAxisAlignment.start,
child: Image.asset( children: [
"assets/icons/ic_rounded.png", const Spacer(),
height: 64.0, Center(
), child: Image.asset(
// child: SvgPicture.asset( "assets/icons/ic_rounded.png",
// "assets/images/github.svg", height: 64.0,
// height: 64.0, ),
// ), // child: SvgPicture.asset(
), // "assets/images/github.svg",
const SizedBox(height: 32.0), // height: 64.0,
const Text( // ),
"Válassz fizetési módot, majd folytasd a fizetést a Stripe felületén, hogy aktiváld az előfizetésed.", ),
textAlign: TextAlign.center, const SizedBox(height: 32.0),
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0), const Text(
), "Válassz fizetési módot, majd folytasd a fizetést a Stripe felületén, hogy aktiváld az előfizetésed.",
// const SizedBox(height: 12.0), textAlign: TextAlign.center,
// Card( style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0),
// shape: RoundedRectangleBorder( ),
// borderRadius: BorderRadius.circular(14.0)), // const SizedBox(height: 12.0),
// child: const Padding( // Card(
// padding: EdgeInsets.all(16.0), // shape: RoundedRectangleBorder(
// child: Column( // borderRadius: BorderRadius.circular(14.0)),
// crossAxisAlignment: CrossAxisAlignment.start, // child: const Padding(
// children: [ // padding: EdgeInsets.all(16.0),
// Row( // child: Column(
// children: [ // crossAxisAlignment: CrossAxisAlignment.start,
// Icon(FeatherIcons.alertTriangle, // children: [
// size: 20.0, color: Colors.orange), // Row(
// SizedBox(width: 12.0), // children: [
// Text( // Icon(FeatherIcons.alertTriangle,
// "Figyelem!", // size: 20.0, color: Colors.orange),
// style: TextStyle( // SizedBox(width: 12.0),
// fontSize: 18.0, fontWeight: FontWeight.bold), // Text(
// ), // "Figyelem!",
// ], // style: TextStyle(
// ), // fontSize: 18.0, fontWeight: FontWeight.bold),
// SizedBox(height: 6.0), // ),
// Text( // ],
// "Az automatikus visszairányítás az alkalmazásba nem mindig működik. Ebben az esetben kérjük nyomd meg lent a \"Manuális aktiválás\" gombot!", // ),
// style: TextStyle(fontSize: 16.0), // SizedBox(height: 6.0),
// ), // Text(
// ], // "Az automatikus visszairányítás az alkalmazásba nem mindig működik. Ebben az esetben kérjük nyomd meg lent a \"Manuális aktiválás\" gombot!",
// ), // style: TextStyle(fontSize: 16.0),
// ), // ),
// ), // ],
const SizedBox(height: 12.0), // ),
Card( // ),
shape: RoundedRectangleBorder( // ),
borderRadius: BorderRadius.circular(14.0)), const SizedBox(height: 12.0),
child: const Padding( Card(
padding: EdgeInsets.all(16.0), shape: RoundedRectangleBorder(
child: Column( borderRadius: BorderRadius.circular(14.0)),
crossAxisAlignment: CrossAxisAlignment.start, child: const Padding(
children: [ padding: EdgeInsets.all(16.0),
Row( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Icon(FeatherIcons.alertTriangle, children: [
size: 20.0, color: Colors.orange), Row(
SizedBox(width: 12.0), children: [
Text( Icon(FeatherIcons.alertTriangle,
"Figyelem!", size: 20.0, color: Colors.orange),
style: TextStyle( SizedBox(width: 12.0),
fontSize: 18.0, fontWeight: FontWeight.bold), Text(
), "Figyelem!",
], style: TextStyle(
), fontSize: 18.0, fontWeight: FontWeight.bold),
SizedBox(height: 6.0), ),
Text( ],
"Az aktiválás azonnal történik, amint kifizetted a szolgáltatás díját. A szolgáltatás automatikusan megújul, lemondásra a beállításokban lesz lehetőséget.", ),
style: TextStyle(fontSize: 16.0), SizedBox(height: 6.0),
), Text(
], "Az aktiválás azonnal történik, amint kifizetted a szolgáltatás díját. A szolgáltatás automatikusan megújul, lemondásra a beállításokban lesz lehetőséget.",
), style: TextStyle(fontSize: 16.0),
), ),
), ],
const SizedBox(height: 12.0), ),
Card( ),
shape: RoundedRectangleBorder( ),
borderRadius: BorderRadius.circular(14.0)), const SizedBox(height: 12.0),
child: Padding( Card(
padding: const EdgeInsets.all(16.0), shape: RoundedRectangleBorder(
child: Column( borderRadius: BorderRadius.circular(14.0)),
crossAxisAlignment: CrossAxisAlignment.start, child: Padding(
children: [ padding: const EdgeInsets.all(16.0),
const Text( child: Column(
"Ha fizetés után a Stripe nem irányít vissza az alkalmazásba automatikusan, aktiválhatod a támogatásod a munkamenet azonosítóval, melyet kimásolhatsz a hibás URL \"session_id\" paraméteréből.", crossAxisAlignment: CrossAxisAlignment.start,
style: children: [
TextStyle(fontSize: 15.0, fontWeight: FontWeight.w500), const Text(
), "Ha fizetés után a Stripe nem irányít vissza az alkalmazásba automatikusan, aktiválhatod a támogatásod a munkamenet azonosítóval, melyet kimásolhatsz a hibás URL \"session_id\" paraméteréből.",
const SizedBox(height: 6.0), style:
Center( TextStyle(fontSize: 15.0, fontWeight: FontWeight.w500),
child: TextButton.icon( ),
onPressed: onManualActivation, const SizedBox(height: 6.0),
style: ButtonStyle( Center(
foregroundColor: WidgetStatePropertyAll( child: TextButton.icon(
Theme.of(context).colorScheme.secondary), onPressed: onManualActivation,
overlayColor: WidgetStatePropertyAll(Theme.of(context) style: ButtonStyle(
.colorScheme foregroundColor: WidgetStatePropertyAll(
.secondary Theme.of(context).colorScheme.secondary),
.withOpacity(.1)), overlayColor: WidgetStatePropertyAll(Theme.of(context)
), .colorScheme
icon: manualActivationLoading .secondary
? const SizedBox( .withOpacity(.1)),
height: 16.0, ),
width: 16.0, icon: manualActivationLoading
child: CircularProgressIndicator(), ? const SizedBox(
) height: 16.0,
: const Icon(FeatherIcons.key, size: 20.0), width: 16.0,
label: const Padding( child: CircularProgressIndicator(),
padding: EdgeInsets.only(left: 8.0), )
child: Text( : const Icon(FeatherIcons.key, size: 20.0),
"Aktiválás azonosítóval", label: const Padding(
style: TextStyle(fontSize: 16.0), padding: EdgeInsets.only(left: 8.0),
), child: Text(
), "Aktiválás azonosítóval",
), style: TextStyle(fontSize: 16.0),
), ),
], ),
), ),
), ),
), ],
const Spacer(), ),
Padding( ),
padding: const EdgeInsets.only(bottom: 24.0), ),
child: Center( const Spacer(),
child: TextButton.icon( Padding(
onPressed: () { padding: const EdgeInsets.only(bottom: 24.0),
Navigator.of(context).pop(); child: Center(
}, child: TextButton.icon(
style: ButtonStyle( onPressed: () {
foregroundColor: Navigator.of(context).pop();
WidgetStatePropertyAll(AppColors.of(context).text), },
overlayColor: WidgetStatePropertyAll( style: ButtonStyle(
AppColors.of(context).text.withOpacity(.1)), foregroundColor:
), WidgetStatePropertyAll(AppColors.of(context).text),
icon: const Icon(FeatherIcons.arrowLeft, size: 20.0), overlayColor: WidgetStatePropertyAll(
label: const Text( AppColors.of(context).text.withOpacity(.1)),
"Vissza", ),
style: TextStyle(fontSize: 16.0), icon: const Icon(FeatherIcons.arrowLeft, size: 20.0),
), label: const Text(
), "Vissza",
), style: TextStyle(fontSize: 16.0),
), ),
], ),
), ),
); ),
} ],
} ),
);
}
}

View File

@ -1,97 +1,103 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_dashboard.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_dashboard.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc_plus/ui/mobile/plus/plus_things.i18n.dart'; import 'package:refilc_plus/ui/mobile/plus/plus_things.i18n.dart';
class PremiumActivationView extends StatefulWidget { class PremiumActivationView extends StatefulWidget {
const PremiumActivationView({super.key, required this.product}); const PremiumActivationView({
super.key,
final String product; required this.product,
required this.paymentProvider,
@override });
State<PremiumActivationView> createState() => _PremiumActivationViewState();
} final String product;
final String paymentProvider;
class _PremiumActivationViewState extends State<PremiumActivationView>
with SingleTickerProviderStateMixin { @override
late AnimationController animation; State<PremiumActivationView> createState() => _PremiumActivationViewState();
bool activated = false; }
@override class _PremiumActivationViewState extends State<PremiumActivationView>
void initState() { with SingleTickerProviderStateMixin {
super.initState(); late AnimationController animation;
context.read<PlusProvider>().auth.initAuth(product: widget.product); bool activated = false;
animation = @override
AnimationController(vsync: this, duration: const Duration(seconds: 2)); void initState() {
} super.initState();
context.read<PlusProvider>().auth.initAuth(
@override product: widget.product, paymentProvider: widget.paymentProvider);
void dispose() {
animation.dispose(); animation =
super.dispose(); AnimationController(vsync: this, duration: const Duration(seconds: 2));
} }
@override @override
Widget build(BuildContext context) { void dispose() {
final premium = context.watch<PlusProvider>(); animation.dispose();
super.dispose();
if (premium.hasPremium && !activated) { }
activated = true;
animation.forward(); @override
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { Widget build(BuildContext context) {
Future.delayed(const Duration(seconds: 5)).then((value) { final premium = context.watch<PlusProvider>();
if (mounted) {
// pop the anim if (premium.hasPremium && !activated) {
Navigator.of(context).pop(); activated = true;
// pop the plus view animation.forward();
Navigator.of(context).pop(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// show alert to save code Future.delayed(const Duration(seconds: 5)).then((value) {
ScaffoldMessenger.of(context).clearSnackBars(); if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( // pop the anim
SnackBar( Navigator.of(context).pop();
content: Text( // pop the plus view
"copy_code_asap".i18n, Navigator.of(context).pop();
textAlign: TextAlign.center, // show alert to save code
style: TextStyle( ScaffoldMessenger.of(context).clearSnackBars();
color: AppColors.of(context).text, ScaffoldMessenger.of(context).showSnackBar(
fontWeight: FontWeight.w600, SnackBar(
), content: Text(
), "copy_code_asap".i18n,
backgroundColor: AppColors.of(context).background, textAlign: TextAlign.center,
), style: TextStyle(
); color: AppColors.of(context).text,
} fontWeight: FontWeight.w600,
}); ),
}); ),
} backgroundColor: AppColors.of(context).background,
),
return Scaffold( );
body: PageTransitionSwitcher( }
transitionBuilder: (child, primaryAnimation, secondaryAnimation) => });
SharedAxisTransition( });
animation: primaryAnimation, }
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal, return Scaffold(
fillColor: Colors.transparent, body: PageTransitionSwitcher(
child: child, transitionBuilder: (child, primaryAnimation, secondaryAnimation) =>
), SharedAxisTransition(
child: premium.hasPremium animation: primaryAnimation,
? Center( secondaryAnimation: secondaryAnimation,
child: SizedBox( transitionType: SharedAxisTransitionType.horizontal,
width: 400, fillColor: Colors.transparent,
child: Lottie.network( child: child,
"https://assets2.lottiefiles.com/packages/lf20_wkebwzpz.json", ),
controller: animation, child: premium.hasPremium
), ? Center(
), child: SizedBox(
) width: 400,
: const SafeArea(child: ActivationDashboard()), child: Lottie.network(
), "https://assets2.lottiefiles.com/packages/lf20_wkebwzpz.json",
); controller: animation,
} ),
} ),
)
: const SafeArea(child: ActivationDashboard()),
),
);
}
}

View File

@ -1,138 +1,138 @@
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String { extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
// upsell titles // upsell titles
"u_title_1": "Use more accounts?", "u_title_1": "Use more accounts?",
"u_title_2": "Want to try the updates in advance?", "u_title_2": "Want to try the updates in advance?",
"u_title_3": "\"Hi kitty, do you have an owner?\"", "u_title_3": "\"Hi kitty, do you have an owner?\"",
"u_title_4": "Would you write down your tasks?", "u_title_4": "Would you write down your tasks?",
"u_title_5": "Lazy to do maths?", "u_title_5": "Lazy to do maths?",
"u_title_6": "I know, the plain grey is not very nice :P", "u_title_6": "I know, the plain grey is not very nice :P",
"u_title_7": "u_title_7":
"\"What were we doing in class? Was there English homework??\"", "\"What were we doing in class? Was there English homework??\"",
"u_title_8": "Now that's something special!", "u_title_8": "Now that's something special!",
"u_title_9": "Woah! What beautiful letters!", "u_title_9": "Woah! What beautiful letters!",
"u_title_10": "Need more suggestions?", "u_title_10": "Need more suggestions?",
"u_title_11": "Not epic, but ultra-super?", "u_title_11": "Not epic, but ultra-super?",
"u_title_12": "Do you even need it in your calendar?!", "u_title_12": "Do you even need it in your calendar?!",
"u_title_13": "Wanna see your past years' grades?", "u_title_13": "Wanna see your past years' grades?",
// upsell descriptions // upsell descriptions
"u_desc_1": "The limit increases with each tier.", "u_desc_1": "The limit increases with each tier.",
"u_desc_2": "u_desc_2":
"Subscribe to reFilc+ to receive beta updates in advance.", "Subscribe to reFilc+ to receive beta updates in advance.",
"u_desc_3": "For a unique greeting, just the lowest tier is enough!", "u_desc_3": "For a unique greeting, just the lowest tier is enough!",
"u_desc_4": "u_desc_4":
"Support us and make a note of all your important things.", "Support us and make a note of all your important things.",
"u_desc_5": "u_desc_5":
"reFilc+ makes it easier to calculate your projected average.", "reFilc+ makes it easier to calculate your projected average.",
"u_desc_6": "With Gold tier, you can recolour to anything.", "u_desc_6": "With Gold tier, you can recolour to anything.",
"u_desc_7": "No more questions in Gold.", "u_desc_7": "No more questions in Gold.",
"u_desc_8": "Upgrade to Gold to change the app icon.", "u_desc_8": "Upgrade to Gold to change the app icon.",
"u_desc_9": "You can also change the font with Gold tier.", "u_desc_9": "You can also change the font with Gold tier.",
"u_desc_10": "u_desc_10":
"Support us on Gold tier and use all the features of goal setting!", "Support us on Gold tier and use all the features of goal setting!",
"u_desc_11": "With reFilc+ lowest tier it's also available!", "u_desc_11": "With reFilc+ lowest tier it's also available!",
"u_desc_12": "Sync your time-table with reFilc+ Gold!", "u_desc_12": "Sync your time-table with reFilc+ Gold!",
"u_desc_13": "You can export every year's grades with reFilc+!", "u_desc_13": "You can export every year's grades with reFilc+!",
// button // button
"subscribe": "Subscribe", "subscribe": "Subscribe",
"subscriber": "Subscribed", "subscriber": "Subscribed",
// other // other
"copy_code_asap": "copy_code_asap":
"Copy your reFilc+ ID, in case you loose your device!", "Copy your reFilc+ ID, in case you loose your device!",
}, },
"hu_hu": { "hu_hu": {
// upsell titles // upsell titles
"u_title_1": "Több fiókot használnál?", "u_title_1": "Több fiókot használnál?",
"u_title_2": "Előre kipróbálnád a frissítéseket?", "u_title_2": "Előre kipróbálnád a frissítéseket?",
"u_title_3": "\"Szia cica, van gazdád?\"", "u_title_3": "\"Szia cica, van gazdád?\"",
"u_title_4": "Felírnád a feladataid?", "u_title_4": "Felírnád a feladataid?",
"u_title_5": "Lusta vagy matekozni?", "u_title_5": "Lusta vagy matekozni?",
"u_title_6": "Tudom, nem túl szép a sima szürke :P", "u_title_6": "Tudom, nem túl szép a sima szürke :P",
"u_title_7": "\"Mit is csináltunk órán? Volt angol házi??\"", "u_title_7": "\"Mit is csináltunk órán? Volt angol házi??\"",
"u_title_8": "Ez aztán különleges!", "u_title_8": "Ez aztán különleges!",
"u_title_9": "Woah! Micsoda gyönyörű betűk!", "u_title_9": "Woah! Micsoda gyönyörű betűk!",
"u_title_10": "Még több javaslat kell?", "u_title_10": "Még több javaslat kell?",
"u_title_11": "Nem epikus, hanem ultraszuper?", "u_title_11": "Nem epikus, hanem ultraszuper?",
"u_title_12": "Még a naptáradba is kell?!", "u_title_12": "Még a naptáradba is kell?!",
"u_title_13": "Szeretnéd látni az előző évi jegyeid?", "u_title_13": "Szeretnéd látni az előző évi jegyeid?",
// upsell descriptions // upsell descriptions
"u_desc_1": "Minden támogatási szinttel egyre magasabb a limit.", "u_desc_1": "Minden támogatási szinttel egyre magasabb a limit.",
"u_desc_2": "u_desc_2":
"Fizess elő reFilc+-ra, hogy előre megkapd a béta frissítéseket.", "Fizess elő reFilc+-ra, hogy előre megkapd a béta frissítéseket.",
"u_desc_3": "Az egyedi üdvözléshez elég csupán a legalsó szint!", "u_desc_3": "Az egyedi üdvözléshez elég csupán a legalsó szint!",
"u_desc_4": "Támogass minket, és jegyezd fel minden fontos dolgod.", "u_desc_4": "Támogass minket, és jegyezd fel minden fontos dolgod.",
"u_desc_5": "reFilc+-al egyszerűbb kiszámolnod a tervezett átlagod.", "u_desc_5": "reFilc+-al egyszerűbb kiszámolnod a tervezett átlagod.",
"u_desc_6": "Gold szintű támogatással átszínezhetsz bármilyenre.", "u_desc_6": "Gold szintű támogatással átszínezhetsz bármilyenre.",
"u_desc_7": "Nincs több ilyen kérdés, ha Gold szinten támogatsz.", "u_desc_7": "Nincs több ilyen kérdés, ha Gold szinten támogatsz.",
"u_desc_8": "u_desc_8":
"Fizess elő Gold szintre az alkalmazás ikonjának megváltoztatásához.", "Fizess elő Gold szintre az alkalmazás ikonjának megváltoztatásához.",
"u_desc_9": "u_desc_9":
"Gold szintű támogatással megváltoztathatod a betűtípust is.", "Gold szintű támogatással megváltoztathatod a betűtípust is.",
"u_desc_10": "u_desc_10":
"Támogass Gold szinten és használd ki a cél kitűzés minden funkcióját!", "Támogass Gold szinten és használd ki a cél kitűzés minden funkcióját!",
"u_desc_11": "A reFilc+ alap szintjével ez is elérhető!", "u_desc_11": "A reFilc+ alap szintjével ez is elérhető!",
"u_desc_12": "Szinkronizáld az órarended reFilc+ Gold-al!", "u_desc_12": "Szinkronizáld az órarended reFilc+ Gold-al!",
"u_desc_13": "Minden évi jegyedet exportálhatod reFilc+-al!", "u_desc_13": "Minden évi jegyedet exportálhatod reFilc+-al!",
// button // button
"subscribe": "Előfizetés", "subscribe": "Előfizetés",
"subscriber": "Előfizetve", "subscriber": "Előfizetve",
// other // other
"copy_code_asap": "copy_code_asap":
"Másold ki a reFilc+ ID-t, mielőtt elveszítenéd a telefonod!", "Másold ki a reFilc+ ID-t, mielőtt elveszítenéd a telefonod!",
}, },
"de_de": { "de_de": {
// upsell titles // upsell titles
"u_title_1": "Mehr Accounts nutzen?", "u_title_1": "Mehr Accounts nutzen?",
"u_title_2": "Willst du die Updates im vorraus testen?", "u_title_2": "Willst du die Updates im vorraus testen?",
"u_title_3": "\"Hallo mein Kätzchen, hast du einen besitzer?\"", "u_title_3": "\"Hallo mein Kätzchen, hast du einen besitzer?\"",
"u_title_4": "Würdest du deine Aufgaben aufschreiben?", "u_title_4": "Würdest du deine Aufgaben aufschreiben?",
"u_title_5": "Faul um Mathe zu machen?", "u_title_5": "Faul um Mathe zu machen?",
"u_title_6": "Ich weiß, das schlichte Grau ist nicht so toll :P", "u_title_6": "Ich weiß, das schlichte Grau ist nicht so toll :P",
"u_title_7": "u_title_7":
"\"Was haben wir im Unterricht gemacht? Gab es Englisch Hausaufgaben??\"", "\"Was haben wir im Unterricht gemacht? Gab es Englisch Hausaufgaben??\"",
"u_title_8": "Na das ist mal was besonderes!", "u_title_8": "Na das ist mal was besonderes!",
"u_title_9": "Wow! Was für schöne Texte!", "u_title_9": "Wow! Was für schöne Texte!",
"u_title_10": "Brauchst du mehr Vorschläge?", "u_title_10": "Brauchst du mehr Vorschläge?",
"u_title_11": "u_title_11":
"Willst du vielleicht statt episch etwas anderes wie super?", "Willst du vielleicht statt episch etwas anderes wie super?",
"u_title_12": "Brauchst du das wirklich in deinem Kalender?!", "u_title_12": "Brauchst du das wirklich in deinem Kalender?!",
"u_title_13": "Möchtest du deine Noten der vergangenen Jahre sehen?", "u_title_13": "Möchtest du deine Noten der vergangenen Jahre sehen?",
// upsell descriptions // upsell descriptions
"u_desc_1": "Das limit erhöht sich mit jedem Abo-plan.", "u_desc_1": "Das limit erhöht sich mit jedem Abo-plan.",
"u_desc_2": "u_desc_2":
"Abonniere reFilc+ um Beta Updates im vorraus zu erhalten.", "Abonniere reFilc+ um Beta Updates im vorraus zu erhalten.",
"u_desc_3": "u_desc_3":
"Für eine eigene Begrüßung ist der niedrigste Abo-Plan schon genug!", "Für eine eigene Begrüßung ist der niedrigste Abo-Plan schon genug!",
"u_desc_4": "u_desc_4":
"Unterstütze uns und schreib alles wichtige für dich auf.", "Unterstütze uns und schreib alles wichtige für dich auf.",
"u_desc_5": "u_desc_5":
"reFilc+ macht es einfacher deinen Durchschnitt zu berechnen.", "reFilc+ macht es einfacher deinen Durchschnitt zu berechnen.",
"u_desc_6": "Mit dem Gold-Plan, kannst du alles anders Färben.", "u_desc_6": "Mit dem Gold-Plan, kannst du alles anders Färben.",
"u_desc_7": "Keine weiteren Fragen mit Gold.", "u_desc_7": "Keine weiteren Fragen mit Gold.",
"u_desc_8": "Upgrade auf Gold um das App Icon zu ändern.", "u_desc_8": "Upgrade auf Gold um das App Icon zu ändern.",
"u_desc_9": "u_desc_9":
"Du kannst mit dem Gold-Plan auch die Schriftart verändern.", "Du kannst mit dem Gold-Plan auch die Schriftart verändern.",
"u_desc_10": "u_desc_10":
"Unterstütze uns mit einem Gold-Plan und benutze alle features des Ziel-setzens!", "Unterstütze uns mit einem Gold-Plan und benutze alle features des Ziel-setzens!",
"u_desc_11": "u_desc_11":
"Mit reFilc+ niedrigstem Abo-Plan ist es auch Verfügbar!", "Mit reFilc+ niedrigstem Abo-Plan ist es auch Verfügbar!",
"u_desc_12": "Synchronisiere deinen Stundenplan mit reFilc+ Gold!", "u_desc_12": "Synchronisiere deinen Stundenplan mit reFilc+ Gold!",
"u_desc_13": "Du kannst jede Jahresnote mit reFilc+ exportieren!", "u_desc_13": "Du kannst jede Jahresnote mit reFilc+ exportieren!",
// button // button
"subscribe": "Abonnieren", "subscribe": "Abonnieren",
"subscriber": "im Abonnement", "subscriber": "im Abonnement",
// other // other
"copy_code_asap": "copy_code_asap":
"Kopieren Sie Ihre reFilc+ ID, bevor Sie Ihr Handy verlieren!", "Kopieren Sie Ihre reFilc+ ID, bevor Sie Ihr Handy verlieren!",
}, },
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params); String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t); String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);
} }

View File

@ -1,74 +1,74 @@
// ignore_for_file: unused_element // ignore_for_file: unused_element
import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum PremiumInlineFeature { nickname, theme, widget, goal, stats } enum PremiumInlineFeature { nickname, theme, widget, goal, stats }
const Map<PremiumInlineFeature, String> _featureAssets = { const Map<PremiumInlineFeature, String> _featureAssets = {
PremiumInlineFeature.nickname: PremiumInlineFeature.nickname:
"assets/images/premium_nickname_inline_showcase.png", "assets/images/premium_nickname_inline_showcase.png",
PremiumInlineFeature.theme: "assets/images/premium_theme_inline_showcase.png", PremiumInlineFeature.theme: "assets/images/premium_theme_inline_showcase.png",
PremiumInlineFeature.widget: PremiumInlineFeature.widget:
"assets/images/premium_widget_inline_showcase.png", "assets/images/premium_widget_inline_showcase.png",
PremiumInlineFeature.goal: "assets/images/premium_goal_inline_showcase.png", PremiumInlineFeature.goal: "assets/images/premium_goal_inline_showcase.png",
PremiumInlineFeature.stats: "assets/images/premium_stats_inline_showcase.png", PremiumInlineFeature.stats: "assets/images/premium_stats_inline_showcase.png",
}; };
const Map<PremiumInlineFeature, PremiumFeature> _featuresInline = { const Map<PremiumInlineFeature, PremiumFeature> _featuresInline = {
PremiumInlineFeature.nickname: PremiumFeature.profile, PremiumInlineFeature.nickname: PremiumFeature.profile,
PremiumInlineFeature.theme: PremiumFeature.customcolors, PremiumInlineFeature.theme: PremiumFeature.customcolors,
PremiumInlineFeature.widget: PremiumFeature.widget, PremiumInlineFeature.widget: PremiumFeature.widget,
// PremiumInlineFeature.goal: PremiumFeature.goalplanner, // PremiumInlineFeature.goal: PremiumFeature.goalplanner,
PremiumInlineFeature.stats: PremiumFeature.gradestats, PremiumInlineFeature.stats: PremiumFeature.gradestats,
}; };
class PremiumInline extends StatelessWidget { class PremiumInline extends StatelessWidget {
const PremiumInline({super.key, required this.features}); const PremiumInline({super.key, required this.features});
final List<PremiumInlineFeature> features; final List<PremiumInlineFeature> features;
String _getAsset() { String _getAsset() {
for (int i = 0; i < features.length; i++) { for (int i = 0; i < features.length; i++) {
if (DateTime.now().day % features.length == i) { if (DateTime.now().day % features.length == i) {
return _featureAssets[features[i]]!; return _featureAssets[features[i]]!;
} }
} }
return _featureAssets[features[0]]!; return _featureAssets[features[0]]!;
} }
PremiumFeature _getFeature() { PremiumFeature _getFeature() {
for (int i = 0; i < features.length; i++) { for (int i = 0; i < features.length; i++) {
if (DateTime.now().day % features.length == i) { if (DateTime.now().day % features.length == i) {
return _featuresInline[features[i]]!; return _featuresInline[features[i]]!;
} }
} }
return _featuresInline[features[0]]!; return _featuresInline[features[0]]!;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const SizedBox( return const SizedBox(
height: 0, height: 0,
); );
// return Stack( // return Stack(
// children: [ // children: [
// Image.asset(_getAsset()), // Image.asset(_getAsset()),
// Positioned.fill( // Positioned.fill(
// child: Material( // child: Material(
// type: MaterialType.transparency, // type: MaterialType.transparency,
// child: InkWell( // child: InkWell(
// borderRadius: BorderRadius.circular(16.0), // borderRadius: BorderRadius.circular(16.0),
// onTap: () { // onTap: () {
// PlusLockedFeaturePopup.show( // PlusLockedFeaturePopup.show(
// context: context, feature: _getFeature()); // context: context, feature: _getFeature());
// }, // },
// ), // ),
// ), // ),
// ), // ),
// ], // ],
// ); // );
} }
} }

View File

@ -1,91 +1,91 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'plus_things.i18n.dart'; import 'plus_things.i18n.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
class PlusSettingsInline extends StatelessWidget { class PlusSettingsInline extends StatelessWidget {
const PlusSettingsInline({super.key}); const PlusSettingsInline({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String plusTier = Provider.of<PlusProvider>(context) final String plusTier = Provider.of<PlusProvider>(context)
.hasScope(PremiumScopes.tierGold) .hasScope(PremiumScopes.tierGold)
? 'gold' ? 'gold'
: (Provider.of<PlusProvider>(context).hasScope(PremiumScopes.tierBasic) : (Provider.of<PlusProvider>(context).hasScope(PremiumScopes.tierBasic)
? 'basic' ? 'basic'
: 'none'); : 'none');
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0), padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (plusTier == 'none') { if (plusTier == 'none') {
Navigator.of(context, rootNavigator: true) Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(builder: (context) { .push(MaterialPageRoute(builder: (context) {
return const PlusScreen(); return const PlusScreen();
})); }));
} else { } else {
SettingsHelper.plusOptions(context); SettingsHelper.plusOptions(context);
} }
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: plusTier == 'gold' image: plusTier == 'gold'
? const AssetImage('assets/images/btn_plus_gold.png') ? const AssetImage('assets/images/btn_plus_gold.png')
: const AssetImage('assets/images/btn_plus_standard.png'), : const AssetImage('assets/images/btn_plus_standard.png'),
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
), ),
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
), ),
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
width: 2.0, width: 2.0,
), ),
Image.asset( Image.asset(
plusTier == 'gold' plusTier == 'gold'
? 'assets/images/plus_tier_ink.png' ? 'assets/images/plus_tier_ink.png'
: 'assets/images/plus_tier_cap.png', : 'assets/images/plus_tier_cap.png',
width: 23.0, width: 23.0,
height: 23.0, height: 23.0,
), ),
const SizedBox( const SizedBox(
width: 14.0, width: 14.0,
), ),
Text( Text(
'reFilc+', 'reFilc+',
style: TextStyle( style: TextStyle(
color: plusTier == 'gold' color: plusTier == 'gold'
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF150D4E), : const Color(0xFF150D4E),
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
], ],
), ),
Text( Text(
plusTier == 'none' ? '0.99 €' : 'subscriber'.i18n, plusTier == 'none' ? '0.99 €' : 'subscriber'.i18n,
style: const TextStyle( style: const TextStyle(
color: Color(0xFF150D4E), color: Color(0xFF150D4E),
fontSize: 15.0, fontSize: 15.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,486 +1,486 @@
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'plus_things.i18n.dart'; import 'plus_things.i18n.dart';
enum PremiumFeature { enum PremiumFeature {
// old things // old things
gradestats, gradestats,
customcolors, customcolors,
profile, profile,
iconpack, iconpack,
subjectrename, subjectrename,
weeklytimetable, weeklytimetable,
widget, widget,
// new things // new things
moreAccounts, // cap, (ink, sponge) moreAccounts, // cap, (ink, sponge)
betaReleases, // cap betaReleases, // cap
welcomeMessage, // cap welcomeMessage, // cap
selfNotes, // cap selfNotes, // cap
gradeCalculation, // ink gradeCalculation, // ink
liveActivity, // ink liveActivity, // ink
timetableNotes, // ink timetableNotes, // ink
iconChange, // sponge iconChange, // sponge
fontChange, // sponge fontChange, // sponge
goalPlanner, // sponge goalPlanner, // sponge
gradeRarities, gradeRarities,
calendarSync, calendarSync,
gradeExporting, // basic gradeExporting, // basic
} }
enum PremiumFeatureLevel { enum PremiumFeatureLevel {
old, old,
cap, cap,
ink, ink,
sponge, sponge,
// new new new // new new new
basic, basic,
gold, gold,
} }
const Map<PremiumFeature, PremiumFeatureLevel> _featureLevels = { const Map<PremiumFeature, PremiumFeatureLevel> _featureLevels = {
// old things // old things
PremiumFeature.gradestats: PremiumFeatureLevel.old, PremiumFeature.gradestats: PremiumFeatureLevel.old,
PremiumFeature.customcolors: PremiumFeatureLevel.old, PremiumFeature.customcolors: PremiumFeatureLevel.old,
PremiumFeature.profile: PremiumFeatureLevel.old, PremiumFeature.profile: PremiumFeatureLevel.old,
PremiumFeature.iconpack: PremiumFeatureLevel.old, PremiumFeature.iconpack: PremiumFeatureLevel.old,
PremiumFeature.subjectrename: PremiumFeatureLevel.old, PremiumFeature.subjectrename: PremiumFeatureLevel.old,
PremiumFeature.weeklytimetable: PremiumFeatureLevel.old, PremiumFeature.weeklytimetable: PremiumFeatureLevel.old,
PremiumFeature.widget: PremiumFeatureLevel.old, PremiumFeature.widget: PremiumFeatureLevel.old,
// new things // new things
PremiumFeature.moreAccounts: PremiumFeatureLevel.basic, PremiumFeature.moreAccounts: PremiumFeatureLevel.basic,
PremiumFeature.betaReleases: PremiumFeatureLevel.basic, PremiumFeature.betaReleases: PremiumFeatureLevel.basic,
PremiumFeature.welcomeMessage: PremiumFeatureLevel.basic, PremiumFeature.welcomeMessage: PremiumFeatureLevel.basic,
PremiumFeature.selfNotes: PremiumFeatureLevel.basic, PremiumFeature.selfNotes: PremiumFeatureLevel.basic,
PremiumFeature.gradeCalculation: PremiumFeatureLevel.basic, PremiumFeature.gradeCalculation: PremiumFeatureLevel.basic,
PremiumFeature.liveActivity: PremiumFeatureLevel.gold, PremiumFeature.liveActivity: PremiumFeatureLevel.gold,
PremiumFeature.timetableNotes: PremiumFeatureLevel.gold, PremiumFeature.timetableNotes: PremiumFeatureLevel.gold,
PremiumFeature.iconChange: PremiumFeatureLevel.gold, PremiumFeature.iconChange: PremiumFeatureLevel.gold,
PremiumFeature.fontChange: PremiumFeatureLevel.gold, PremiumFeature.fontChange: PremiumFeatureLevel.gold,
PremiumFeature.goalPlanner: PremiumFeatureLevel.gold, PremiumFeature.goalPlanner: PremiumFeatureLevel.gold,
PremiumFeature.gradeRarities: PremiumFeatureLevel.basic, PremiumFeature.gradeRarities: PremiumFeatureLevel.basic,
PremiumFeature.calendarSync: PremiumFeatureLevel.gold, PremiumFeature.calendarSync: PremiumFeatureLevel.gold,
PremiumFeature.gradeExporting: PremiumFeatureLevel.basic, PremiumFeature.gradeExporting: PremiumFeatureLevel.basic,
}; };
// const Map<PremiumFeature, String> _featureAssets = { // const Map<PremiumFeature, String> _featureAssets = {
// // old // // old
// PremiumFeature.gradestats: "assets/images/premium_stats_showcase.png", // PremiumFeature.gradestats: "assets/images/premium_stats_showcase.png",
// PremiumFeature.customcolors: "assets/images/premium_theme_showcase.png", // PremiumFeature.customcolors: "assets/images/premium_theme_showcase.png",
// PremiumFeature.profile: "assets/images/premium_nickname_showcase.png", // PremiumFeature.profile: "assets/images/premium_nickname_showcase.png",
// PremiumFeature.weeklytimetable: // PremiumFeature.weeklytimetable:
// "assets/images/premium_timetable_showcase.png", // "assets/images/premium_timetable_showcase.png",
// // PremiumFeature.goalplanner: "assets/images/premium_goal_showcase.png", // // PremiumFeature.goalplanner: "assets/images/premium_goal_showcase.png",
// PremiumFeature.widget: "assets/images/premium_widget_showcase.png", // PremiumFeature.widget: "assets/images/premium_widget_showcase.png",
// // new // // new
// PremiumFeature.moreAccounts: "assets/images/premium_banner/more_accounts.png", // PremiumFeature.moreAccounts: "assets/images/premium_banner/more_accounts.png",
// PremiumFeature.betaReleases: "assets/images/premium_banner/beta_releases.png", // PremiumFeature.betaReleases: "assets/images/premium_banner/beta_releases.png",
// PremiumFeature.welcomeMessage: // PremiumFeature.welcomeMessage:
// "assets/images/premium_banner/welcome_message.png", // "assets/images/premium_banner/welcome_message.png",
// PremiumFeature.selfNotes: "assets/images/premium_banner/self_notes.png", // PremiumFeature.selfNotes: "assets/images/premium_banner/self_notes.png",
// PremiumFeature.gradeCalculation: // PremiumFeature.gradeCalculation:
// "assets/images/premium_banner/grade_calc.png", // "assets/images/premium_banner/grade_calc.png",
// PremiumFeature.liveActivity: "assets/images/premium_banner/live_activity.png", // PremiumFeature.liveActivity: "assets/images/premium_banner/live_activity.png",
// PremiumFeature.timetableNotes: // PremiumFeature.timetableNotes:
// "assets/images/premium_banner/timetable_notes.png", // "assets/images/premium_banner/timetable_notes.png",
// PremiumFeature.iconChange: "assets/images/premium_banner/app_icon.png", // PremiumFeature.iconChange: "assets/images/premium_banner/app_icon.png",
// PremiumFeature.fontChange: "assets/images/premium_banner/font.png", // PremiumFeature.fontChange: "assets/images/premium_banner/font.png",
// PremiumFeature.goalPlanner: "assets/images/premium_banner/goal_planner.png", // PremiumFeature.goalPlanner: "assets/images/premium_banner/goal_planner.png",
// PremiumFeature.gradeRarities: // PremiumFeature.gradeRarities:
// "assets/images/premium_banner/grade_rarities.png", // "assets/images/premium_banner/grade_rarities.png",
// PremiumFeature.calendarSync: "assets/images/premium_banner/calendar_sync.png", // PremiumFeature.calendarSync: "assets/images/premium_banner/calendar_sync.png",
// }; // };
const Map<PremiumFeature, String> _featureTitles = { const Map<PremiumFeature, String> _featureTitles = {
// old shit // old shit
PremiumFeature.gradestats: "Találtál egy prémium funkciót.", PremiumFeature.gradestats: "Találtál egy prémium funkciót.",
PremiumFeature.customcolors: "Több személyre szabás kell?", PremiumFeature.customcolors: "Több személyre szabás kell?",
PremiumFeature.profile: "Nem tetszik a neved?", PremiumFeature.profile: "Nem tetszik a neved?",
PremiumFeature.iconpack: "Jobban tetszettek a régi ikonok?", PremiumFeature.iconpack: "Jobban tetszettek a régi ikonok?",
PremiumFeature.subjectrename: PremiumFeature.subjectrename:
"Sokáig tart elolvasni, hogy \"Földrajz természettudomány\"?", "Sokáig tart elolvasni, hogy \"Földrajz természettudomány\"?",
PremiumFeature.weeklytimetable: "Szeretnéd egyszerre az egész hetet látni?", PremiumFeature.weeklytimetable: "Szeretnéd egyszerre az egész hetet látni?",
// PremiumFeature.goalplanner: "Kövesd a céljaidat, sok-sok statisztikával.", // PremiumFeature.goalplanner: "Kövesd a céljaidat, sok-sok statisztikával.",
PremiumFeature.widget: "Órák a kezdőképernyőd kényelméből.", PremiumFeature.widget: "Órák a kezdőképernyőd kényelméből.",
// new shit // new shit
PremiumFeature.moreAccounts: "u_title_1", PremiumFeature.moreAccounts: "u_title_1",
PremiumFeature.betaReleases: "u_title_2", PremiumFeature.betaReleases: "u_title_2",
PremiumFeature.welcomeMessage: "u_title_3", PremiumFeature.welcomeMessage: "u_title_3",
PremiumFeature.selfNotes: "u_title_4", PremiumFeature.selfNotes: "u_title_4",
PremiumFeature.gradeCalculation: "u_title_5", PremiumFeature.gradeCalculation: "u_title_5",
PremiumFeature.liveActivity: "u_title_6", PremiumFeature.liveActivity: "u_title_6",
PremiumFeature.timetableNotes: "u_title_7", PremiumFeature.timetableNotes: "u_title_7",
PremiumFeature.iconChange: "u_title_8", PremiumFeature.iconChange: "u_title_8",
PremiumFeature.fontChange: "u_title_9", PremiumFeature.fontChange: "u_title_9",
PremiumFeature.goalPlanner: "u_title_10", PremiumFeature.goalPlanner: "u_title_10",
PremiumFeature.gradeRarities: "u_title_11", PremiumFeature.gradeRarities: "u_title_11",
PremiumFeature.calendarSync: "u_title_12", PremiumFeature.calendarSync: "u_title_12",
PremiumFeature.gradeExporting: "u_title_13", PremiumFeature.gradeExporting: "u_title_13",
}; };
const Map<PremiumFeature, String> _featureDescriptions = { const Map<PremiumFeature, String> _featureDescriptions = {
// old // old
PremiumFeature.gradestats: PremiumFeature.gradestats:
"Támogass Kupak szinten, hogy több statisztikát láthass. ", "Támogass Kupak szinten, hogy több statisztikát láthass. ",
PremiumFeature.customcolors: PremiumFeature.customcolors:
"Támogass Kupak szinten, és szabd személyre az elemek, a háttér, és a panelek színeit.", "Támogass Kupak szinten, és szabd személyre az elemek, a háttér, és a panelek színeit.",
PremiumFeature.profile: PremiumFeature.profile:
"Kupak szinten változtathatod a nevedet, sőt, akár a profilképedet is.", "Kupak szinten változtathatod a nevedet, sőt, akár a profilképedet is.",
PremiumFeature.iconpack: PremiumFeature.iconpack:
"Támogass Kupak szinten, hogy ikon témát választhass.", "Támogass Kupak szinten, hogy ikon témát választhass.",
PremiumFeature.subjectrename: PremiumFeature.subjectrename:
"Támogass Kupak szinten, hogy átnevezhesd Föcire.", "Támogass Kupak szinten, hogy átnevezhesd Föcire.",
PremiumFeature.weeklytimetable: PremiumFeature.weeklytimetable:
"Támogass Tinta szinten a heti órarend funkcióért.", "Támogass Tinta szinten a heti órarend funkcióért.",
// PremiumFeature.goalplanner: "A célkövetéshez támogass Tinta szinten.", // PremiumFeature.goalplanner: "A célkövetéshez támogass Tinta szinten.",
PremiumFeature.widget: PremiumFeature.widget:
"Támogass Tinta szinten, és helyezz egy widgetet a kezdőképernyődre.", "Támogass Tinta szinten, és helyezz egy widgetet a kezdőképernyődre.",
// new // new
PremiumFeature.moreAccounts: "u_desc_1", PremiumFeature.moreAccounts: "u_desc_1",
PremiumFeature.betaReleases: "u_desc_2", PremiumFeature.betaReleases: "u_desc_2",
PremiumFeature.welcomeMessage: "u_desc_3", PremiumFeature.welcomeMessage: "u_desc_3",
PremiumFeature.selfNotes: "u_desc_4", PremiumFeature.selfNotes: "u_desc_4",
PremiumFeature.gradeCalculation: "u_desc_5", PremiumFeature.gradeCalculation: "u_desc_5",
PremiumFeature.liveActivity: "u_desc_6", PremiumFeature.liveActivity: "u_desc_6",
PremiumFeature.timetableNotes: "u_desc_7", PremiumFeature.timetableNotes: "u_desc_7",
PremiumFeature.iconChange: "u_desc_8", PremiumFeature.iconChange: "u_desc_8",
PremiumFeature.fontChange: "u_desc_9", PremiumFeature.fontChange: "u_desc_9",
PremiumFeature.goalPlanner: "u_desc_10", PremiumFeature.goalPlanner: "u_desc_10",
PremiumFeature.gradeRarities: "u_desc_11", PremiumFeature.gradeRarities: "u_desc_11",
PremiumFeature.calendarSync: "u_desc_12", PremiumFeature.calendarSync: "u_desc_12",
PremiumFeature.gradeExporting: "u_desc_13", PremiumFeature.gradeExporting: "u_desc_13",
}; };
// class PremiumLockedFeatureUpsell extends StatelessWidget { // class PremiumLockedFeatureUpsell extends StatelessWidget {
// const PremiumLockedFeatureUpsell({super.key, required this.feature}); // const PremiumLockedFeatureUpsell({super.key, required this.feature});
// static void show( // static void show(
// {required BuildContext context, required PremiumFeature feature}) => // {required BuildContext context, required PremiumFeature feature}) =>
// showRoundedModalBottomSheet(context, // showRoundedModalBottomSheet(context,
// child: PremiumLockedFeatureUpsell(feature: feature)); // child: PremiumLockedFeatureUpsell(feature: feature));
// final PremiumFeature feature; // final PremiumFeature feature;
// IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap // IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap
// ? FilcIcons.kupak // ? FilcIcons.kupak
// : _featureLevels[feature] == PremiumFeatureLevel.ink // : _featureLevels[feature] == PremiumFeatureLevel.ink
// ? FilcIcons.tinta // ? FilcIcons.tinta
// : FilcIcons.tinta; // : FilcIcons.tinta;
// Color _getColor(BuildContext context) => // Color _getColor(BuildContext context) =>
// _featureLevels[feature] == PremiumFeatureLevel.gold // _featureLevels[feature] == PremiumFeatureLevel.gold
// ? const Color(0xFFC89B08) // ? const Color(0xFFC89B08)
// : Theme.of(context).brightness == Brightness.light // : Theme.of(context).brightness == Brightness.light
// ? const Color(0xff691A9B) // ? const Color(0xff691A9B)
// : const Color(0xffA66FC8); // : const Color(0xffA66FC8);
// String? _getAsset() => _featureAssets[feature]; // String? _getAsset() => _featureAssets[feature];
// String _getTitle() => _featureTitles[feature]!; // String _getTitle() => _featureTitles[feature]!;
// String _getDescription() => _featureDescriptions[feature]!; // String _getDescription() => _featureDescriptions[feature]!;
// @override // @override
// Widget build(BuildContext context) { // Widget build(BuildContext context) {
// final Color color = _getColor(context); // final Color color = _getColor(context);
// return Dialog( // return Dialog(
// child: Padding( // child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0), // padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0),
// child: Column( // child: Column(
// mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
// children: [ // children: [
// // Title Bar // // Title Bar
// Row( // Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [ // children: [
// Padding( // Padding(
// padding: const EdgeInsets.only(left: 8.0), // padding: const EdgeInsets.only(left: 8.0),
// child: Icon(_getIcon()), // child: Icon(_getIcon()),
// ), // ),
// IconButton( // IconButton(
// onPressed: () => Navigator.of(context).pop(), // onPressed: () => Navigator.of(context).pop(),
// icon: const Icon(Icons.close), // icon: const Icon(Icons.close),
// ), // ),
// ], // ],
// ), // ),
// // Image showcase // // Image showcase
// if (_getAsset() != null) // if (_getAsset() != null)
// Padding( // Padding(
// padding: const EdgeInsets.only(top: 8.0), // padding: const EdgeInsets.only(top: 8.0),
// child: Image.asset(_getAsset()!), // child: Image.asset(_getAsset()!),
// ), // ),
// // Dialog title // // Dialog title
// Padding( // Padding(
// padding: const EdgeInsets.only(top: 12.0), // padding: const EdgeInsets.only(top: 12.0),
// child: Text( // child: Text(
// _getTitle(), // _getTitle(),
// style: const TextStyle( // style: const TextStyle(
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// fontSize: 20.0, // fontSize: 20.0,
// ), // ),
// ), // ),
// ), // ),
// // Dialog description // // Dialog description
// Padding( // Padding(
// padding: const EdgeInsets.only(top: 8.0), // padding: const EdgeInsets.only(top: 8.0),
// child: Text( // child: Text(
// _getDescription(), // _getDescription(),
// style: const TextStyle( // style: const TextStyle(
// fontSize: 16.0, // fontSize: 16.0,
// ), // ),
// ), // ),
// ), // ),
// // CTA button // // CTA button
// Padding( // Padding(
// padding: const EdgeInsets.only(top: 8.0), // padding: const EdgeInsets.only(top: 8.0),
// child: SizedBox( // child: SizedBox(
// width: double.infinity, // width: double.infinity,
// child: TextButton( // child: TextButton(
// style: ButtonStyle( // style: ButtonStyle(
// backgroundColor: // backgroundColor:
// WidgetStatePropertyAll(color.withOpacity(.25)), // WidgetStatePropertyAll(color.withOpacity(.25)),
// foregroundColor: WidgetStatePropertyAll(color), // foregroundColor: WidgetStatePropertyAll(color),
// overlayColor: // overlayColor:
// WidgetStatePropertyAll(color.withOpacity(.1))), // WidgetStatePropertyAll(color.withOpacity(.1))),
// onPressed: () { // onPressed: () {
// Navigator.of(context, rootNavigator: true) // Navigator.of(context, rootNavigator: true)
// .push(MaterialPageRoute(builder: (context) { // .push(MaterialPageRoute(builder: (context) {
// return const PlusScreen(); // return const PlusScreen();
// })); // }));
// }, // },
// child: const Text( // child: const Text(
// "Vigyél oda!", // "Vigyél oda!",
// style: TextStyle( // style: TextStyle(
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// fontSize: 18.0, // fontSize: 18.0,
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ], // ],
// ), // ),
// ), // ),
// ); // );
// } // }
// } // }
class PlusLockedFeaturePopup extends StatelessWidget { class PlusLockedFeaturePopup extends StatelessWidget {
const PlusLockedFeaturePopup({super.key, required this.feature}); const PlusLockedFeaturePopup({super.key, required this.feature});
static void show({ static void show({
required BuildContext context, required BuildContext context,
required PremiumFeature feature, required PremiumFeature feature,
}) => }) =>
showRoundedModalBottomSheet( showRoundedModalBottomSheet(
context, context,
child: PlusLockedFeaturePopup( child: PlusLockedFeaturePopup(
feature: feature, feature: feature,
), ),
showHandle: false, showHandle: false,
); );
final PremiumFeature feature; final PremiumFeature feature;
PremiumFeatureLevel? _getFeatureLevel() => _featureLevels[feature]; PremiumFeatureLevel? _getFeatureLevel() => _featureLevels[feature];
// IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap // IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap
// ? FilcIcons.kupak // ? FilcIcons.kupak
// : _featureLevels[feature] == PremiumFeatureLevel.ink // : _featureLevels[feature] == PremiumFeatureLevel.ink
// ? FilcIcons.tinta // ? FilcIcons.tinta
// : FilcIcons.tinta; // : FilcIcons.tinta;
// Color _getColor(BuildContext context) => // Color _getColor(BuildContext context) =>
// _featureLevels[feature] == PremiumFeatureLevel.gold // _featureLevels[feature] == PremiumFeatureLevel.gold
// ? const Color(0xFFC89B08) // ? const Color(0xFFC89B08)
// : Theme.of(context).brightness == Brightness.light // : Theme.of(context).brightness == Brightness.light
// ? const Color(0xff691A9B) // ? const Color(0xff691A9B)
// : const Color(0xffA66FC8); // : const Color(0xffA66FC8);
// String? _getAsset() => _featureAssets[feature]; // String? _getAsset() => _featureAssets[feature];
String _getTitle() => _featureTitles[feature]!.i18n; String _getTitle() => _featureTitles[feature]!.i18n;
String _getDescription() => _featureDescriptions[feature]!.i18n; String _getDescription() => _featureDescriptions[feature]!.i18n;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isGold = _getFeatureLevel() == PremiumFeatureLevel.gold; final bool isGold = _getFeatureLevel() == PremiumFeatureLevel.gold;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isGold ? const Color(0xFFF7EDD9) : const Color(0xFFDCDAF7), color: isGold ? const Color(0xFFF7EDD9) : const Color(0xFFDCDAF7),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0), top: Radius.circular(12.0),
), ),
), ),
child: Stack( child: Stack(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
// "assets/svg/mesh_bg.svg", // "assets/svg/mesh_bg.svg",
"assets/svg/cover_arts/grid.svg", "assets/svg/cover_arts/grid.svg",
// ignore: deprecated_member_use // ignore: deprecated_member_use
color: isGold ? const Color(0xFFf0dcb6) : const Color(0xFFbcb8f0), color: isGold ? const Color(0xFFf0dcb6) : const Color(0xFFbcb8f0),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
), ),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
child: Padding( child: Padding(
padding: const EdgeInsets.all(18.0), padding: const EdgeInsets.all(18.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Container( Container(
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isGold color: isGold
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF130667), : const Color(0xFF130667),
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
2.0, 2.0,
), ),
), ),
), ),
const SizedBox( const SizedBox(
height: 38.0, height: 38.0,
), ),
Image.asset( Image.asset(
'assets/images/plus_${isGold ? 'gold' : 'standard'}.png', 'assets/images/plus_${isGold ? 'gold' : 'standard'}.png',
width: 66, width: 66,
height: 66, height: 66,
), ),
const SizedBox( const SizedBox(
height: 55.0, height: 55.0,
), ),
Container( Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color( color: const Color(
0xFFF7F9FC, 0xFFF7F9FC,
).withOpacity(0.7), ).withOpacity(0.7),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0), top: Radius.circular(12.0),
bottom: Radius.circular(6.0), bottom: Radius.circular(6.0),
), ),
), ),
padding: const EdgeInsets.all(14.0), padding: const EdgeInsets.all(14.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
isGold ? 'reFilc+ Gold' : 'reFilc+', isGold ? 'reFilc+ Gold' : 'reFilc+',
style: TextStyle( style: TextStyle(
color: isGold color: isGold
? const Color(0xFFAD7637) ? const Color(0xFFAD7637)
: const Color(0xFF7463E2), : const Color(0xFF7463E2),
fontSize: 14.0, fontSize: 14.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox( const SizedBox(
height: 12.0, height: 12.0,
), ),
Text( Text(
_getTitle(), _getTitle(),
style: TextStyle( style: TextStyle(
color: isGold color: isGold
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF150D4E), : const Color(0xFF150D4E),
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Text( Text(
_getDescription(), _getDescription(),
style: TextStyle( style: TextStyle(
color: isGold color: isGold
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF150D4E), : const Color(0xFF150D4E),
fontSize: 14.0, fontSize: 14.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
const SizedBox( const SizedBox(
height: 6.0, height: 6.0,
), ),
Container( Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color( color: const Color(
0xFFF7F9FC, 0xFFF7F9FC,
).withOpacity(0.7), ).withOpacity(0.7),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(6.0), top: Radius.circular(6.0),
bottom: Radius.circular(12.0), bottom: Radius.circular(12.0),
), ),
), ),
padding: const EdgeInsets.all(14.0), padding: const EdgeInsets.all(14.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'A reFilc+ 0.99 €, a reFilc+ Gold 2.99 €', 'A reFilc+ 0.99 €, a reFilc+ Gold 2.99 €',
style: TextStyle( style: TextStyle(
color: isGold color: isGold
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF150D4E), : const Color(0xFF150D4E),
fontSize: 14.0, fontSize: 14.0,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
], ],
), ),
), ),
const SizedBox( const SizedBox(
height: 24.0, height: 24.0,
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context, rootNavigator: true) Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(builder: (context) { .push(MaterialPageRoute(builder: (context) {
return const PlusScreen(); return const PlusScreen();
})); }));
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage( image: AssetImage(
'assets/images/btn_plus_${isGold ? 'gold' : 'standard'}.png'), 'assets/images/btn_plus_${isGold ? 'gold' : 'standard'}.png'),
), ),
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
), ),
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
'subscribe'.i18n, 'subscribe'.i18n,
style: TextStyle( style: TextStyle(
color: isGold color: isGold
? const Color(0xFF341C01) ? const Color(0xFF341C01)
: const Color(0xFF150D4E), : const Color(0xFF150D4E),
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
); );
} }
} }

View File

@ -1,242 +1,242 @@
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart';
// import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; // import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_plus/helpers/app_icon_helper.dart'; import 'package:refilc_plus/helpers/app_icon_helper.dart';
// import 'package:refilc_plus/models/premium_scopes.dart'; // import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/plus_provider.dart'; // import 'package:refilc_plus/providers/plus_provider.dart';
// import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; // import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'app_icon_screen.i18n.dart'; import 'app_icon_screen.i18n.dart';
class PremiumCustomAppIconMenu extends StatelessWidget { class PremiumCustomAppIconMenu extends StatelessWidget {
const PremiumCustomAppIconMenu({super.key, required this.settings}); const PremiumCustomAppIconMenu({super.key, required this.settings});
final SettingsProvider settings; final SettingsProvider settings;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// return PanelButton( // return PanelButton(
// onPressed: () { // onPressed: () {
// if (!Provider.of<PlusProvider>(context, listen: false) // if (!Provider.of<PlusProvider>(context, listen: false)
// .hasScope(PremiumScopes.changeAppIcon)) { // .hasScope(PremiumScopes.changeAppIcon)) {
// PlusLockedFeaturePopup.show( // PlusLockedFeaturePopup.show(
// context: context, feature: PremiumFeature.appiconchange); // context: context, feature: PremiumFeature.appiconchange);
// return; // return;
// } // }
// Navigator.of(context, rootNavigator: true).push( // Navigator.of(context, rootNavigator: true).push(
// CupertinoPageRoute(builder: (context) => const ModifyAppIcon()), // CupertinoPageRoute(builder: (context) => const ModifyAppIcon()),
// ); // );
// }, // },
// title: Text('custom_app_icon'.i18n), // title: Text('custom_app_icon'.i18n),
// leading: const Icon(FeatherIcons.edit), // leading: const Icon(FeatherIcons.edit),
// ); // );
return const SizedBox( return const SizedBox(
width: 0, width: 0,
height: 0, height: 0,
); );
} }
} }
class ModifyAppIcon extends StatefulWidget { class ModifyAppIcon extends StatefulWidget {
const ModifyAppIcon({super.key}); const ModifyAppIcon({super.key});
@override @override
State<ModifyAppIcon> createState() => _ModifyAppIconState(); State<ModifyAppIcon> createState() => _ModifyAppIconState();
} }
class _ModifyAppIconState extends State<ModifyAppIcon> { class _ModifyAppIconState extends State<ModifyAppIcon> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late SettingsProvider settings; late SettingsProvider settings;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
settings = Provider.of<SettingsProvider>(context); settings = Provider.of<SettingsProvider>(context);
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
appBar: AppBar( appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text), leading: BackButton(color: AppColors.of(context).text),
title: Text( title: Text(
"app_icons".i18n, "app_icons".i18n,
style: TextStyle(color: AppColors.of(context).text), style: TextStyle(color: AppColors.of(context).text),
), ),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Panel( Panel(
title: Text("basic".i18n), title: Text("basic".i18n),
child: Column( child: Column(
children: [ children: [
AppIconItem( AppIconItem(
iconName: 'refilc_default', iconName: 'refilc_default',
iconPath: 'assets/launch_icons/refilc_default.png', iconPath: 'assets/launch_icons/refilc_default.png',
displayName: 'reFilc Default', displayName: 'reFilc Default',
description: 'Az alapértelmezett ikon.', description: 'Az alapértelmezett ikon.',
selected: settings.appIcon == 'refilc_default', selected: settings.appIcon == 'refilc_default',
selectCallback: () async { selectCallback: () async {
await AppIconHelper.setAppIcon('refilc_default'); await AppIconHelper.setAppIcon('refilc_default');
settings.update(appIcon: 'refilc_default'); settings.update(appIcon: 'refilc_default');
}, },
), ),
], ],
), ),
), ),
// const SizedBox(height: 16.0), // const SizedBox(height: 16.0),
// Panel( // Panel(
// title: Text("seasonal".i18n), // title: Text("seasonal".i18n),
// child: Column( // child: Column(
// children: [ // children: [
// // AppIconItem( // // AppIconItem(
// // iconName: 'refilc_default', // // iconName: 'refilc_default',
// // iconPath: 'assets/launch_icons/refilc_default.png', // // iconPath: 'assets/launch_icons/refilc_default.png',
// // displayName: 'reFilc Default', // // displayName: 'reFilc Default',
// // description: 'Az alapértelmezett ikon.', // // description: 'Az alapértelmezett ikon.',
// // selected: true, // // selected: true,
// // selectCallback: () {}, // // selectCallback: () {},
// // ), // // ),
// ], // ],
// ), // ),
// ), // ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Panel( Panel(
title: Text("special".i18n), title: Text("special".i18n),
child: Column( child: Column(
children: [ children: [
AppIconItem( AppIconItem(
iconName: 'refilc_overcomplicated', iconName: 'refilc_overcomplicated',
iconPath: iconPath:
'assets/launch_icons/refilc_overcomplicated.png', 'assets/launch_icons/refilc_overcomplicated.png',
displayName: 'Overcomplicated', displayName: 'Overcomplicated',
// description: 'Egy túlkomplikált ikon.', // description: 'Egy túlkomplikált ikon.',
selected: settings.appIcon == 'refilc_overcomplicated', selected: settings.appIcon == 'refilc_overcomplicated',
selectCallback: () async { selectCallback: () async {
await AppIconHelper.setAppIcon( await AppIconHelper.setAppIcon(
'refilc_overcomplicated'); 'refilc_overcomplicated');
settings.update(appIcon: 'refilc_overcomplicated'); settings.update(appIcon: 'refilc_overcomplicated');
}, },
), ),
AppIconItem( AppIconItem(
iconName: 'refilc_concept', iconName: 'refilc_concept',
iconPath: 'assets/launch_icons/refilc_concept.png', iconPath: 'assets/launch_icons/refilc_concept.png',
displayName: 'Modern Concept', displayName: 'Modern Concept',
// description: 'Egy modernebb, letisztultabb ikon.', // description: 'Egy modernebb, letisztultabb ikon.',
selected: settings.appIcon == 'refilc_concept', selected: settings.appIcon == 'refilc_concept',
selectCallback: () async { selectCallback: () async {
await AppIconHelper.setAppIcon('refilc_concept'); await AppIconHelper.setAppIcon('refilc_concept');
settings.update(appIcon: 'refilc_concept'); settings.update(appIcon: 'refilc_concept');
}, },
), ),
], ],
), ),
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Panel( Panel(
title: Text("other".i18n), title: Text("other".i18n),
child: Column( child: Column(
children: [ children: [
AppIconItem( AppIconItem(
iconName: 'refilc_pride', iconName: 'refilc_pride',
iconPath: 'assets/launch_icons/refilc_pride.png', iconPath: 'assets/launch_icons/refilc_pride.png',
displayName: 'Pride', displayName: 'Pride',
// description: '', // description: '',
selected: settings.appIcon == 'refilc_pride', selected: settings.appIcon == 'refilc_pride',
selectCallback: () async { selectCallback: () async {
await AppIconHelper.setAppIcon('refilc_pride'); await AppIconHelper.setAppIcon('refilc_pride');
settings.update(appIcon: 'refilc_pride'); settings.update(appIcon: 'refilc_pride');
}, },
), ),
], ],
), ),
), ),
], ],
), ),
), ),
)); ));
} }
} }
class AppIconItem extends StatelessWidget { class AppIconItem extends StatelessWidget {
const AppIconItem({ const AppIconItem({
super.key, super.key,
required this.iconName, required this.iconName,
required this.iconPath, required this.iconPath,
required this.displayName, required this.displayName,
this.description, this.description,
required this.selected, required this.selected,
required this.selectCallback, required this.selectCallback,
}); });
final String iconName; final String iconName;
final String iconPath; final String iconPath;
final String displayName; final String displayName;
final String? description; final String? description;
final bool selected; final bool selected;
final void Function() selectCallback; final void Function() selectCallback;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
minLeadingWidth: 32.0, minLeadingWidth: 32.0,
dense: true, dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 0), contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onTap: () {}, onTap: () {},
leading: Container( leading: Container(
height: 40, height: 40,
width: 40, width: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
image: DecorationImage( image: DecorationImage(
image: AssetImage(iconPath), image: AssetImage(iconPath),
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
title: InkWell( title: InkWell(
onTap: selectCallback, onTap: selectCallback,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
displayName, displayName,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 16, fontSize: 16,
height: description == null ? 3.2 : 1.8, height: description == null ? 3.2 : 1.8,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (description != null) if (description != null)
Text( Text(
description!, description!,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14, fontSize: 14,
color: AppColors.of(context).text.withOpacity(.75), color: AppColors.of(context).text.withOpacity(.75),
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ],
), ),
), ),
trailing: selected trailing: selected
? Icon( ? Icon(
FeatherIcons.checkCircle, FeatherIcons.checkCircle,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
) )
: const SizedBox(), : const SizedBox(),
); );
} }
} }

View File

@ -1,36 +1,36 @@
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String { extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
"custom_app_icon": "Custom App Icon", "custom_app_icon": "Custom App Icon",
"app_icons": "App Icons", "app_icons": "App Icons",
"basic": "Basic", "basic": "Basic",
"seasonal": "Seasonal", "seasonal": "Seasonal",
"special": "Special", "special": "Special",
"other": "Other", "other": "Other",
}, },
"hu_hu": { "hu_hu": {
"custom_app_icon": "Alkalmazásikon", "custom_app_icon": "Alkalmazásikon",
"app_icons": "Alkalmazásikonok", "app_icons": "Alkalmazásikonok",
"basic": "Egyszerű", "basic": "Egyszerű",
"seasonal": "Szezonális", "seasonal": "Szezonális",
"special": "Különleges", "special": "Különleges",
"other": "Egyéb", "other": "Egyéb",
}, },
"de_de": { "de_de": {
"custom_app_icon": "App-Symbol", "custom_app_icon": "App-Symbol",
"app_icons": "App-Symbole", "app_icons": "App-Symbole",
"basic": "Basic", "basic": "Basic",
"seasonal": "Saisonal", "seasonal": "Saisonal",
"special": "Besonders", "special": "Besonders",
"other": "Andere", "other": "Andere",
}, },
}; };
String get i18n => localize(this, _t); String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params); String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t); String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);
} }

View File

@ -1,452 +1,452 @@
// import 'package:dropdown_button2/dropdown_button2.dart'; // import 'package:dropdown_button2/dropdown_button2.dart';
// import 'package:refilc/api/providers/database_provider.dart'; // import 'package:refilc/api/providers/database_provider.dart';
// import 'package:refilc/api/providers/user_provider.dart'; // import 'package:refilc/api/providers/user_provider.dart';
// import 'package:refilc/models/settings.dart'; // import 'package:refilc/models/settings.dart';
// import 'package:refilc/theme/colors/colors.dart'; // import 'package:refilc/theme/colors/colors.dart';
// import 'package:refilc/utils/format.dart'; // import 'package:refilc/utils/format.dart';
// import 'package:refilc_kreta_api/models/teacher.dart'; // import 'package:refilc_kreta_api/models/teacher.dart';
// import 'package:refilc_kreta_api/providers/absence_provider.dart'; // import 'package:refilc_kreta_api/providers/absence_provider.dart';
// import 'package:refilc_kreta_api/providers/grade_provider.dart'; // import 'package:refilc_kreta_api/providers/grade_provider.dart';
// import 'package:refilc_kreta_api/providers/timetable_provider.dart'; // import 'package:refilc_kreta_api/providers/timetable_provider.dart';
// import 'package:refilc_mobile_ui/common/panel/panel.dart'; // import 'package:refilc_mobile_ui/common/panel/panel.dart';
// import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; // import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
// // import 'package:refilc_plus/models/premium_scopes.dart'; // // import 'package:refilc_plus/models/premium_scopes.dart';
// // import 'package:refilc_plus/providers/plus_provider.dart'; // // import 'package:refilc_plus/providers/plus_provider.dart';
// // import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; // // import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
// import 'package:flutter/cupertino.dart'; // import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
// import 'package:flutter_feather_icons/flutter_feather_icons.dart'; // import 'package:flutter_feather_icons/flutter_feather_icons.dart';
// import 'package:provider/provider.dart'; // import 'package:provider/provider.dart';
// import 'package:refilc_mobile_ui/screens/settings/modify_names.i18n.dart'; // import 'package:refilc_mobile_ui/screens/settings/modify_names.i18n.dart';
// class MenuRenamedTeachers extends StatelessWidget { // class MenuRenamedTeachers extends StatelessWidget {
// const MenuRenamedTeachers({Key? key, required this.settings}) // const MenuRenamedTeachers({Key? key, required this.settings})
// : super(key: key); // : super(key: key);
// final SettingsProvider settings; // final SettingsProvider settings;
// @override // @override
// Widget build(BuildContext context) { // Widget build(BuildContext context) {
// return PanelButton( // return PanelButton(
// padding: const EdgeInsets.only(left: 14.0), // padding: const EdgeInsets.only(left: 14.0),
// onPressed: () { // onPressed: () {
// // if (!Provider.of<PlusProvider>(context, listen: false) // // if (!Provider.of<PlusProvider>(context, listen: false)
// // .hasScope(PremiumScopes.renameTeachers)) { // // .hasScope(PremiumScopes.renameTeachers)) {
// // PlusLockedFeaturePopup.show( // // PlusLockedFeaturePopup.show(
// // context: context, feature: PremiumFeature.teacherrename); // // context: context, feature: PremiumFeature.teacherrename);
// // return; // // return;
// // } // // }
// Navigator.of(context, rootNavigator: true).push( // Navigator.of(context, rootNavigator: true).push(
// CupertinoPageRoute(builder: (context) => const ModifyTeacherNames()), // CupertinoPageRoute(builder: (context) => const ModifyTeacherNames()),
// ); // );
// }, // },
// title: Text( // title: Text(
// "rename_teachers".i18n, // "rename_teachers".i18n,
// style: TextStyle( // style: TextStyle(
// color: AppColors.of(context) // color: AppColors.of(context)
// .text // .text
// .withOpacity(settings.renamedTeachersEnabled ? 1.0 : .5)), // .withOpacity(settings.renamedTeachersEnabled ? 1.0 : .5)),
// ), // ),
// leading: settings.renamedTeachersEnabled // leading: settings.renamedTeachersEnabled
// ? const Icon(FeatherIcons.users) // ? const Icon(FeatherIcons.users)
// : Icon(FeatherIcons.users, // : Icon(FeatherIcons.users,
// color: AppColors.of(context).text.withOpacity(.25)), // color: AppColors.of(context).text.withOpacity(.25)),
// trailingDivider: true, // trailingDivider: true,
// trailing: Switch( // trailing: Switch(
// onChanged: (v) async { // onChanged: (v) async {
// // if (!Provider.of<PlusProvider>(context, listen: false) // // if (!Provider.of<PlusProvider>(context, listen: false)
// // .hasScope(PremiumScopes.renameTeachers)) { // // .hasScope(PremiumScopes.renameTeachers)) {
// // PlusLockedFeaturePopup.show( // // PlusLockedFeaturePopup.show(
// // context: context, feature: PremiumFeature.teacherrename); // // context: context, feature: PremiumFeature.teacherrename);
// // return; // // return;
// // } // // }
// settings.update(renamedTeachersEnabled: v); // settings.update(renamedTeachersEnabled: v);
// await Provider.of<GradeProvider>(context, listen: false) // await Provider.of<GradeProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// await Provider.of<TimetableProvider>(context, listen: false) // await Provider.of<TimetableProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// await Provider.of<AbsenceProvider>(context, listen: false) // await Provider.of<AbsenceProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// }, // },
// value: settings.renamedTeachersEnabled, // value: settings.renamedTeachersEnabled,
// activeColor: Theme.of(context).colorScheme.secondary, // activeColor: Theme.of(context).colorScheme.secondary,
// ), // ),
// ); // );
// } // }
// } // }
// class ModifyTeacherNames extends StatefulWidget { // class ModifyTeacherNames extends StatefulWidget {
// const ModifyTeacherNames({Key? key}) : super(key: key); // const ModifyTeacherNames({Key? key}) : super(key: key);
// @override // @override
// State<ModifyTeacherNames> createState() => _ModifyTeacherNamesState(); // State<ModifyTeacherNames> createState() => _ModifyTeacherNamesState();
// } // }
// class _ModifyTeacherNamesState extends State<ModifyTeacherNames> { // class _ModifyTeacherNamesState extends State<ModifyTeacherNames> {
// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); // final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
// final _teacherName = TextEditingController(); // final _teacherName = TextEditingController();
// String? selectedTeacherId; // String? selectedTeacherId;
// late List<Teacher> teachers; // late List<Teacher> teachers;
// late UserProvider user; // late UserProvider user;
// late DatabaseProvider dbProvider; // late DatabaseProvider dbProvider;
// late SettingsProvider settings; // late SettingsProvider settings;
// @override // @override
// void initState() { // void initState() {
// super.initState(); // super.initState();
// teachers = (Provider.of<GradeProvider>(context, listen: false) // teachers = (Provider.of<GradeProvider>(context, listen: false)
// .grades // .grades
// .map((e) => e.teacher) // .map((e) => e.teacher)
// .toSet() // .toSet()
// .toList() // .toList()
// ..sort((a, b) => a.name.compareTo(b.name))); // ..sort((a, b) => a.name.compareTo(b.name)));
// user = Provider.of<UserProvider>(context, listen: false); // user = Provider.of<UserProvider>(context, listen: false);
// dbProvider = Provider.of<DatabaseProvider>(context, listen: false); // dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
// } // }
// Future<Map<String, String>> fetchRenamedTeachers() async { // Future<Map<String, String>> fetchRenamedTeachers() async {
// return await dbProvider.userQuery.renamedTeachers(userId: user.id!); // return await dbProvider.userQuery.renamedTeachers(userId: user.id!);
// } // }
// void showRenameDialog() { // void showRenameDialog() {
// showDialog( // showDialog(
// context: context, // context: context,
// builder: (context) => StatefulBuilder(builder: (context, setS) { // builder: (context) => StatefulBuilder(builder: (context, setS) {
// return AlertDialog( // return AlertDialog(
// shape: const RoundedRectangleBorder( // shape: const RoundedRectangleBorder(
// borderRadius: BorderRadius.all(Radius.circular(14.0))), // borderRadius: BorderRadius.all(Radius.circular(14.0))),
// title: Text("rename_teacher".i18n), // title: Text("rename_teacher".i18n),
// content: Column( // content: Column(
// mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
// children: [ // children: [
// DropdownButton2( // DropdownButton2(
// items: teachers // items: teachers
// .map((item) => DropdownMenuItem<String>( // .map((item) => DropdownMenuItem<String>(
// value: item.id, // value: item.id,
// child: Text( // child: Text(
// item.name, // item.name,
// style: TextStyle( // style: TextStyle(
// fontSize: 14, // fontSize: 14,
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// color: AppColors.of(context).text, // color: AppColors.of(context).text,
// ), // ),
// overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
// ), // ),
// )) // ))
// .toList(), // .toList(),
// onChanged: (String? v) async { // onChanged: (String? v) async {
// final renamedSubs = await fetchRenamedTeachers(); // final renamedSubs = await fetchRenamedTeachers();
// setS(() { // setS(() {
// selectedTeacherId = v; // selectedTeacherId = v;
// if (renamedSubs.containsKey(selectedTeacherId)) { // if (renamedSubs.containsKey(selectedTeacherId)) {
// _teacherName.text = renamedSubs[selectedTeacherId]!; // _teacherName.text = renamedSubs[selectedTeacherId]!;
// } else { // } else {
// _teacherName.text = ""; // _teacherName.text = "";
// } // }
// }); // });
// }, // },
// iconStyleData: IconStyleData( // iconStyleData: IconStyleData(
// iconSize: 14, // iconSize: 14,
// iconEnabledColor: AppColors.of(context).text, // iconEnabledColor: AppColors.of(context).text,
// iconDisabledColor: AppColors.of(context).text, // iconDisabledColor: AppColors.of(context).text,
// ), // ),
// underline: const SizedBox(), // underline: const SizedBox(),
// menuItemStyleData: MenuItemStyleData(height: 40,), // menuItemStyleData: MenuItemStyleData(height: 40,),
// itemHeight: 40, // itemHeight: 40,
// itemPadding: const EdgeInsets.only(left: 14, right: 14), // itemPadding: const EdgeInsets.only(left: 14, right: 14),
// buttonWidth: 50, // buttonWidth: 50,
// dropdownWidth: 300, // dropdownWidth: 300,
// dropdownPadding: null, // dropdownPadding: null,
// buttonDecoration: BoxDecoration( // buttonDecoration: BoxDecoration(
// borderRadius: BorderRadius.circular(8), // borderRadius: BorderRadius.circular(8),
// ), // ),
// dropdownDecoration: BoxDecoration( // dropdownDecoration: BoxDecoration(
// borderRadius: BorderRadius.circular(14), // borderRadius: BorderRadius.circular(14),
// ), // ),
// dropdownElevation: 8, // dropdownElevation: 8,
// scrollbarRadius: const Radius.circular(40), // scrollbarRadius: const Radius.circular(40),
// scrollbarThickness: 6, // scrollbarThickness: 6,
// scrollbarAlwaysShow: true, // scrollbarAlwaysShow: true,
// offset: const Offset(-10, -10), // offset: const Offset(-10, -10),
// buttonSplashColor: Colors.transparent, // buttonSplashColor: Colors.transparent,
// customButton: Container( // customButton: Container(
// width: double.infinity, // width: double.infinity,
// decoration: BoxDecoration( // decoration: BoxDecoration(
// border: Border.all(color: Colors.grey, width: 2), // border: Border.all(color: Colors.grey, width: 2),
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// ), // ),
// padding: const EdgeInsets.symmetric( // padding: const EdgeInsets.symmetric(
// vertical: 12.0, horizontal: 8.0), // vertical: 12.0, horizontal: 8.0),
// child: Text( // child: Text(
// selectedTeacherId == null // selectedTeacherId == null
// ? "select_teacher".i18n // ? "select_teacher".i18n
// : teachers // : teachers
// .firstWhere( // .firstWhere(
// (element) => element.id == selectedTeacherId, // (element) => element.id == selectedTeacherId,
// orElse: () => Teacher( // orElse: () => Teacher(
// id: 'noid', name: "select_teacher".i18n), // id: 'noid', name: "select_teacher".i18n),
// ) // )
// .name, // .name,
// style: Theme.of(context).textTheme.titleSmall!.copyWith( // style: Theme.of(context).textTheme.titleSmall!.copyWith(
// fontWeight: FontWeight.w700, // fontWeight: FontWeight.w700,
// color: AppColors.of(context).text.withOpacity(0.75)), // color: AppColors.of(context).text.withOpacity(0.75)),
// overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
// maxLines: 2, // maxLines: 2,
// textAlign: TextAlign.center, // textAlign: TextAlign.center,
// ), // ),
// ), // ),
// ), // ),
// const Padding( // const Padding(
// padding: EdgeInsets.symmetric(vertical: 8.0), // padding: EdgeInsets.symmetric(vertical: 8.0),
// child: Icon(FeatherIcons.arrowDown, size: 32), // child: Icon(FeatherIcons.arrowDown, size: 32),
// ), // ),
// TextField( // TextField(
// controller: _teacherName, // controller: _teacherName,
// decoration: InputDecoration( // decoration: InputDecoration(
// border: OutlineInputBorder( // border: OutlineInputBorder(
// borderSide: // borderSide:
// const BorderSide(color: Colors.grey, width: 1.5), // const BorderSide(color: Colors.grey, width: 1.5),
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// ), // ),
// focusedBorder: OutlineInputBorder( // focusedBorder: OutlineInputBorder(
// borderSide: // borderSide:
// const BorderSide(color: Colors.grey, width: 1.5), // const BorderSide(color: Colors.grey, width: 1.5),
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// ), // ),
// contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), // contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
// hintText: "modified_name".i18n, // hintText: "modified_name".i18n,
// suffixIcon: IconButton( // suffixIcon: IconButton(
// icon: const Icon( // icon: const Icon(
// FeatherIcons.x, // FeatherIcons.x,
// color: Colors.grey, // color: Colors.grey,
// ), // ),
// onPressed: () { // onPressed: () {
// setState(() { // setState(() {
// _teacherName.text = ""; // _teacherName.text = "";
// }); // });
// }, // },
// ), // ),
// ), // ),
// ), // ),
// ], // ],
// ), // ),
// actions: [ // actions: [
// TextButton( // TextButton(
// child: Text( // child: Text(
// "cancel".i18n, // "cancel".i18n,
// style: const TextStyle(fontWeight: FontWeight.w500), // style: const TextStyle(fontWeight: FontWeight.w500),
// ), // ),
// onPressed: () { // onPressed: () {
// Navigator.of(context).maybePop(); // Navigator.of(context).maybePop();
// }, // },
// ), // ),
// TextButton( // TextButton(
// child: Text( // child: Text(
// "done".i18n, // "done".i18n,
// style: const TextStyle(fontWeight: FontWeight.w500), // style: const TextStyle(fontWeight: FontWeight.w500),
// ), // ),
// onPressed: () async { // onPressed: () async {
// if (selectedTeacherId != null) { // if (selectedTeacherId != null) {
// final renamedSubs = await fetchRenamedTeachers(); // final renamedSubs = await fetchRenamedTeachers();
// renamedSubs[selectedTeacherId!] = _teacherName.text; // renamedSubs[selectedTeacherId!] = _teacherName.text;
// await dbProvider.userStore // await dbProvider.userStore
// .storeRenamedTeachers(renamedSubs, userId: user.id!); // .storeRenamedTeachers(renamedSubs, userId: user.id!);
// await Provider.of<GradeProvider>(context, listen: false) // await Provider.of<GradeProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// await Provider.of<TimetableProvider>(context, listen: false) // await Provider.of<TimetableProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// await Provider.of<AbsenceProvider>(context, listen: false) // await Provider.of<AbsenceProvider>(context, listen: false)
// .convertBySettings(); // .convertBySettings();
// } // }
// Navigator.of(context).pop(true); // Navigator.of(context).pop(true);
// setState(() {}); // setState(() {});
// }, // },
// ), // ),
// ], // ],
// ); // );
// }), // }),
// ).then((val) { // ).then((val) {
// _teacherName.text = ""; // _teacherName.text = "";
// selectedTeacherId = null; // selectedTeacherId = null;
// }); // });
// } // }
// @override // @override
// Widget build(BuildContext context) { // Widget build(BuildContext context) {
// settings = Provider.of<SettingsProvider>(context); // settings = Provider.of<SettingsProvider>(context);
// return Scaffold( // return Scaffold(
// key: _scaffoldKey, // key: _scaffoldKey,
// appBar: AppBar( // appBar: AppBar(
// surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, // surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
// leading: BackButton(color: AppColors.of(context).text), // leading: BackButton(color: AppColors.of(context).text),
// title: Text( // title: Text(
// "modify_teachers".i18n, // "modify_teachers".i18n,
// style: TextStyle(color: AppColors.of(context).text), // style: TextStyle(color: AppColors.of(context).text),
// ), // ),
// ), // ),
// body: Padding( // body: Padding(
// padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), // padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
// child: SingleChildScrollView( // child: SingleChildScrollView(
// child: Column( // child: Column(
// crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
// children: [ // children: [
// // Panel( // // Panel(
// // child: SwitchListTile( // // child: SwitchListTile(
// // title: Text("italics_toggle".i18n), // // title: Text("italics_toggle".i18n),
// // onChanged: (value) => // // onChanged: (value) =>
// // settings.update(renamedTeachersItalics: value), // // settings.update(renamedTeachersItalics: value),
// // value: settings.renamedTeachersItalics, // // value: settings.renamedTeachersItalics,
// // ), // // ),
// // ), // // ),
// // const SizedBox( // // const SizedBox(
// // height: 20, // // height: 20,
// // ), // // ),
// InkWell( // InkWell(
// onTap: showRenameDialog, // onTap: showRenameDialog,
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// child: Container( // child: Container(
// width: double.infinity, // width: double.infinity,
// decoration: BoxDecoration( // decoration: BoxDecoration(
// border: Border.all(color: Colors.grey, width: 2), // border: Border.all(color: Colors.grey, width: 2),
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// ), // ),
// padding: const EdgeInsets.symmetric( // padding: const EdgeInsets.symmetric(
// vertical: 18.0, horizontal: 12.0), // vertical: 18.0, horizontal: 12.0),
// child: Center( // child: Center(
// child: Text( // child: Text(
// "rename_new_teacher".i18n, // "rename_new_teacher".i18n,
// style: TextStyle( // style: TextStyle(
// fontWeight: FontWeight.w600, // fontWeight: FontWeight.w600,
// fontSize: 18, // fontSize: 18,
// color: AppColors.of(context).text.withOpacity(.85), // color: AppColors.of(context).text.withOpacity(.85),
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// ), // ),
// const SizedBox( // const SizedBox(
// height: 30, // height: 30,
// ), // ),
// FutureBuilder<Map<String, String>>( // FutureBuilder<Map<String, String>>(
// future: fetchRenamedTeachers(), // future: fetchRenamedTeachers(),
// builder: (context, snapshot) { // builder: (context, snapshot) {
// if (!snapshot.hasData || snapshot.data!.isEmpty) { // if (!snapshot.hasData || snapshot.data!.isEmpty) {
// return Container(); // return Container();
// } // }
// return Panel( // return Panel(
// title: Text("renamed_teachers".i18n), // title: Text("renamed_teachers".i18n),
// child: Column( // child: Column(
// children: snapshot.data!.keys.map( // children: snapshot.data!.keys.map(
// (key) { // (key) {
// Teacher? teacher = teachers.firstWhere( // Teacher? teacher = teachers.firstWhere(
// (element) => key == element.id, // (element) => key == element.id,
// orElse: () => Teacher(id: 'noid', name: 'noname'), // orElse: () => Teacher(id: 'noid', name: 'noname'),
// ); // );
// if (teacher.id == 'noid') { // if (teacher.id == 'noid') {
// return const SizedBox( // return const SizedBox(
// width: 0, // width: 0,
// height: 0, // height: 0,
// ); // );
// } // }
// String renameTo = snapshot.data![key]!; // String renameTo = snapshot.data![key]!;
// return RenamedTeacherItem( // return RenamedTeacherItem(
// teacher: teacher, // teacher: teacher,
// renamedTo: renameTo, // renamedTo: renameTo,
// modifyCallback: () { // modifyCallback: () {
// setState(() { // setState(() {
// selectedTeacherId = teacher.id; // selectedTeacherId = teacher.id;
// _teacherName.text = renameTo; // _teacherName.text = renameTo;
// }); // });
// showRenameDialog(); // showRenameDialog();
// }, // },
// removeCallback: () { // removeCallback: () {
// setState(() { // setState(() {
// Map<String, String> subs = // Map<String, String> subs =
// Map.from(snapshot.data!); // Map.from(snapshot.data!);
// subs.remove(key); // subs.remove(key);
// dbProvider.userStore.storeRenamedTeachers( // dbProvider.userStore.storeRenamedTeachers(
// subs, // subs,
// userId: user.id!); // userId: user.id!);
// }); // });
// }, // },
// ); // );
// }, // },
// ).toList(), // ).toList(),
// ), // ),
// ); // );
// }, // },
// ), // ),
// ], // ],
// ), // ),
// ), // ),
// )); // ));
// } // }
// } // }
// class RenamedTeacherItem extends StatelessWidget { // class RenamedTeacherItem extends StatelessWidget {
// const RenamedTeacherItem({ // const RenamedTeacherItem({
// Key? key, // Key? key,
// required this.teacher, // required this.teacher,
// required this.renamedTo, // required this.renamedTo,
// required this.modifyCallback, // required this.modifyCallback,
// required this.removeCallback, // required this.removeCallback,
// }) : super(key: key); // }) : super(key: key);
// final Teacher teacher; // final Teacher teacher;
// final String renamedTo; // final String renamedTo;
// final void Function() modifyCallback; // final void Function() modifyCallback;
// final void Function() removeCallback; // final void Function() removeCallback;
// @override // @override
// Widget build(BuildContext context) { // Widget build(BuildContext context) {
// return ListTile( // return ListTile(
// minLeadingWidth: 32.0, // minLeadingWidth: 32.0,
// dense: true, // dense: true,
// contentPadding: // contentPadding:
// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0), // const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
// visualDensity: VisualDensity.compact, // visualDensity: VisualDensity.compact,
// onTap: () {}, // onTap: () {},
// leading: Icon(FeatherIcons.user, // leading: Icon(FeatherIcons.user,
// color: AppColors.of(context).text.withOpacity(.75)), // color: AppColors.of(context).text.withOpacity(.75)),
// title: InkWell( // title: InkWell(
// onTap: modifyCallback, // onTap: modifyCallback,
// child: Column( // child: Column(
// crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
// children: [ // children: [
// Text( // Text(
// teacher.name.capital(), // teacher.name.capital(),
// style: TextStyle( // style: TextStyle(
// fontWeight: FontWeight.w500, // fontWeight: FontWeight.w500,
// fontSize: 14, // fontSize: 14,
// color: AppColors.of(context).text.withOpacity(.75)), // color: AppColors.of(context).text.withOpacity(.75)),
// maxLines: 1, // maxLines: 1,
// overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
// ), // ),
// Text( // Text(
// renamedTo, // renamedTo,
// style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16), // style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
// maxLines: 2, // maxLines: 2,
// overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis,
// ), // ),
// ], // ],
// ), // ),
// ), // ),
// trailing: InkWell( // trailing: InkWell(
// onTap: removeCallback, // onTap: removeCallback,
// child: Icon(FeatherIcons.trash, // child: Icon(FeatherIcons.trash,
// color: AppColors.of(context).red.withOpacity(.75)), // color: AppColors.of(context).red.withOpacity(.75)),
// ), // ),
// ); // );
// } // }
// } // }

View File

@ -1,143 +1,143 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/ui/widgets/grade/grade_tile.dart'; import 'package:refilc/ui/widgets/grade/grade_tile.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
class GradeRarityTextSetting extends StatefulWidget { class GradeRarityTextSetting extends StatefulWidget {
const GradeRarityTextSetting({ const GradeRarityTextSetting({
super.key, super.key,
required this.title, required this.title,
required this.cancel, required this.cancel,
required this.done, required this.done,
required this.defaultRarities, required this.defaultRarities,
}); });
final String title; final String title;
final String cancel; final String cancel;
final String done; final String done;
final List<String> defaultRarities; final List<String> defaultRarities;
@override @override
GradeRarityTextSettingState createState() => GradeRarityTextSettingState(); GradeRarityTextSettingState createState() => GradeRarityTextSettingState();
} }
class GradeRarityTextSettingState extends State<GradeRarityTextSetting> { class GradeRarityTextSettingState extends State<GradeRarityTextSetting> {
late SettingsProvider settings; late SettingsProvider settings;
late DatabaseProvider db; late DatabaseProvider db;
late UserProvider user; late UserProvider user;
final _rarityText = TextEditingController(); final _rarityText = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
settings = Provider.of<SettingsProvider>(context, listen: false); settings = Provider.of<SettingsProvider>(context, listen: false);
db = Provider.of<DatabaseProvider>(context, listen: false); db = Provider.of<DatabaseProvider>(context, listen: false);
user = Provider.of<UserProvider>(context, listen: false); user = Provider.of<UserProvider>(context, listen: false);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column(children: [ return Column(children: [
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (index) { children: List.generate(5, (index) {
return ClipOval( return ClipOval(
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showRenameDialog( showRenameDialog(
title: widget.title, title: widget.title,
cancel: widget.cancel, cancel: widget.cancel,
done: widget.done, done: widget.done,
rarities: rarities:
await db.userQuery.getGradeRarities(userId: user.id!), await db.userQuery.getGradeRarities(userId: user.id!),
gradeIndex: (index + 1).toString(), gradeIndex: (index + 1).toString(),
defaultRarities: widget.defaultRarities, defaultRarities: widget.defaultRarities,
); );
}, },
child: GradeValueWidget(GradeValue(index + 1, "", "", 0), child: GradeValueWidget(GradeValue(index + 1, "", "", 0),
fill: true, size: 36.0), fill: true, size: 36.0),
), ),
), ),
); );
}), }),
), ),
), ),
]); ]);
} }
void showRenameDialog( void showRenameDialog(
{required String title, {required String title,
required String cancel, required String cancel,
required String done, required String done,
required Map<String, String> rarities, required Map<String, String> rarities,
required String gradeIndex, required String gradeIndex,
required List<String> defaultRarities, required List<String> defaultRarities,
required}) { required}) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) { builder: (context) => StatefulBuilder(builder: (context, setS) {
String? rr = rarities[gradeIndex]; String? rr = rarities[gradeIndex];
rr ??= ''; rr ??= '';
_rarityText.text = rr; _rarityText.text = rr;
return AlertDialog( return AlertDialog(
title: Text(title), title: Text(title),
content: TextField( content: TextField(
controller: _rarityText, controller: _rarityText,
autofocus: true, autofocus: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
label: Text(defaultRarities[int.parse(gradeIndex) - 1]), label: Text(defaultRarities[int.parse(gradeIndex) - 1]),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(FeatherIcons.x), icon: const Icon(FeatherIcons.x),
onPressed: () { onPressed: () {
setState(() { setState(() {
_rarityText.clear(); _rarityText.clear();
}); });
}, },
), ),
), ),
), ),
actions: [ actions: [
TextButton( TextButton(
child: Text( child: Text(
cancel, cancel,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
onPressed: () { onPressed: () {
Navigator.of(context).maybePop(); Navigator.of(context).maybePop();
}, },
), ),
TextButton( TextButton(
child: Text( child: Text(
done, done,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
onPressed: () { onPressed: () {
rarities[gradeIndex] = _rarityText.text; rarities[gradeIndex] = _rarityText.text;
Provider.of<DatabaseProvider>(context, listen: false) Provider.of<DatabaseProvider>(context, listen: false)
.userStore .userStore
.storeGradeRarities(rarities, userId: user.id!); .storeGradeRarities(rarities, userId: user.id!);
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
), ),
], ],
); );
}), }),
).then((val) { ).then((val) {
_rarityText.clear(); _rarityText.clear();
}); });
} }
} }

View File

@ -1,26 +1,26 @@
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class PremiumShareTheme extends StatefulWidget { class PremiumShareTheme extends StatefulWidget {
const PremiumShareTheme({super.key}); const PremiumShareTheme({super.key});
@override @override
State<PremiumShareTheme> createState() => _PremiumShareThemeState(); State<PremiumShareTheme> createState() => _PremiumShareThemeState();
} }
class _PremiumShareThemeState extends State<PremiumShareTheme> class _PremiumShareThemeState extends State<PremiumShareTheme>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late final SettingsProvider settingsProvider; late final SettingsProvider settingsProvider;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
settingsProvider = Provider.of<SettingsProvider>(context, listen: false); settingsProvider = Provider.of<SettingsProvider>(context, listen: false);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold(); return const Scaffold();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,363 +1,363 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/providers/third_party_provider.dart'; import 'package:refilc/providers/third_party_provider.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart'; import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart'; import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc_mobile_ui/common/dot.dart'; import 'package:refilc_mobile_ui/common/dot.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:refilc_mobile_ui/common/chips/new_chip.dart'; import 'package:refilc_mobile_ui/common/chips/new_chip.dart';
class MenuGradeExporting extends StatelessWidget { class MenuGradeExporting extends StatelessWidget {
const MenuGradeExporting({ const MenuGradeExporting({
super.key, super.key,
this.borderRadius = const BorderRadius.vertical( this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)), top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
}); });
final BorderRadius borderRadius; final BorderRadius borderRadius;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PanelButton( return PanelButton(
onPressed: () async { onPressed: () async {
// if (!Provider.of<PlusProvider>(context, listen: false) // if (!Provider.of<PlusProvider>(context, listen: false)
// .hasScope(PremiumScopes.calendarSync)) { // .hasScope(PremiumScopes.calendarSync)) {
// return PlusLockedFeaturePopup.show( // return PlusLockedFeaturePopup.show(
// context: context, feature: PremiumFeature.calendarSync); // context: context, feature: PremiumFeature.calendarSync);
// } // }
// Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute( // Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
// builder: (context) => const CalendarSyncScreen())); // builder: (context) => const CalendarSyncScreen()));
// showDialog( // showDialog(
// context: context, // context: context,
// builder: (context) => AlertDialog( // builder: (context) => AlertDialog(
// title: const Text("Figyelem!"), // title: const Text("Figyelem!"),
// content: const Text( // content: const Text(
// "Az exportált jegyek jelenleg még nem megtekinthetők a reFilc-ben, csak te magad tudod átnézni őket JSON formátumban. A jövőben ez a funkció bővülni fog, és a jegyeket meg is tekintheted majd a reFilc felületén."), // "Az exportált jegyek jelenleg még nem megtekinthetők a reFilc-ben, csak te magad tudod átnézni őket JSON formátumban. A jövőben ez a funkció bővülni fog, és a jegyeket meg is tekintheted majd a reFilc felületén."),
// actions: [ // actions: [
// // TextButton( // // TextButton(
// // child: const Text( // // child: const Text(
// // "Vissza", // // "Vissza",
// // style: TextStyle(fontWeight: FontWeight.w500), // // style: TextStyle(fontWeight: FontWeight.w500),
// // ), // // ),
// // onPressed: () { // // onPressed: () {
// // Navigator.of(context).pop(); // // Navigator.of(context).pop();
// // }, // // },
// // ), // // ),
// TextButton( // TextButton(
// child: const Text( // child: const Text(
// "Tovább", // "Tovább",
// style: TextStyle(fontWeight: FontWeight.w500), // style: TextStyle(fontWeight: FontWeight.w500),
// ), // ),
// onPressed: () { // onPressed: () {
// Navigator.of(context).pop(); // Navigator.of(context).pop();
Provider.of<SettingsProvider>(context, listen: false).update( Provider.of<SettingsProvider>(context, listen: false).update(
unseenNewFeatures: List.from( unseenNewFeatures: List.from(
Provider.of<SettingsProvider>(context, listen: false) Provider.of<SettingsProvider>(context, listen: false)
.unseenNewFeatures .unseenNewFeatures
..remove('grade_exporting'), ..remove('grade_exporting'),
), ),
); );
// Provider.of<SettingsProvider>(context, listen: false).update( // Provider.of<SettingsProvider>(context, listen: false).update(
// unseenNewFeatures: ['grade_exporting'], // unseenNewFeatures: ['grade_exporting'],
// ); // );
if (!Provider.of<PlusProvider>(context, listen: false) if (!Provider.of<PlusProvider>(context, listen: false)
.hasScope(PremiumScopes.gradeExporting)) { .hasScope(PremiumScopes.gradeExporting)) {
return PlusLockedFeaturePopup.show( return PlusLockedFeaturePopup.show(
context: context, feature: PremiumFeature.gradeExporting); context: context, feature: PremiumFeature.gradeExporting);
} }
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute( Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
builder: (context) => const GradeExportingScreen())); builder: (context) => const GradeExportingScreen()));
// }, // },
// ), // ),
// ], // ],
// ), // ),
// ); // );
}, },
title: Text( title: Text(
"grade_exporting".i18n, "grade_exporting".i18n,
style: TextStyle( style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95), color: AppColors.of(context).text.withOpacity(.95),
), ),
), ),
leading: Icon( leading: Icon(
Icons.toll_rounded, Icons.toll_rounded,
size: 22.0, size: 22.0,
color: AppColors.of(context).text.withOpacity(.95), color: AppColors.of(context).text.withOpacity(.95),
), ),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (Provider.of<SettingsProvider>(context) if (Provider.of<SettingsProvider>(context)
.unseenNewFeatures .unseenNewFeatures
.contains('grade_exporting')) .contains('grade_exporting'))
const NewChip(), const NewChip(),
Icon( Icon(
FeatherIcons.chevronRight, FeatherIcons.chevronRight,
size: 22.0, size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95), color: AppColors.of(context).text.withOpacity(0.95),
) )
], ],
), ),
borderRadius: borderRadius, borderRadius: borderRadius,
); );
} }
} }
class GradeExportingScreen extends StatefulWidget { class GradeExportingScreen extends StatefulWidget {
const GradeExportingScreen({super.key}); const GradeExportingScreen({super.key});
@override @override
CalendarSyncScreenState createState() => CalendarSyncScreenState(); CalendarSyncScreenState createState() => CalendarSyncScreenState();
} }
class CalendarSyncScreenState extends State<GradeExportingScreen> class CalendarSyncScreenState extends State<GradeExportingScreen>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
late UserProvider user; late UserProvider user;
late ShareProvider shareProvider; late ShareProvider shareProvider;
late ThirdPartyProvider thirdPartyProvider; late ThirdPartyProvider thirdPartyProvider;
late AnimationController _hideContainersController; late AnimationController _hideContainersController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
shareProvider = Provider.of<ShareProvider>(context, listen: false); shareProvider = Provider.of<ShareProvider>(context, listen: false);
_hideContainersController = AnimationController( _hideContainersController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200)); vsync: this, duration: const Duration(milliseconds: 200));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
settingsProvider = Provider.of<SettingsProvider>(context); settingsProvider = Provider.of<SettingsProvider>(context);
user = Provider.of<UserProvider>(context); user = Provider.of<UserProvider>(context);
thirdPartyProvider = Provider.of<ThirdPartyProvider>(context); thirdPartyProvider = Provider.of<ThirdPartyProvider>(context);
return AnimatedBuilder( return AnimatedBuilder(
animation: _hideContainersController, animation: _hideContainersController,
builder: (context, child) => Opacity( builder: (context, child) => Opacity(
opacity: 1 - _hideContainersController.value, opacity: 1 - _hideContainersController.value,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text), leading: BackButton(color: AppColors.of(context).text),
title: Text( title: Text(
"grade_exporting".i18n, "grade_exporting".i18n,
style: TextStyle(color: AppColors.of(context).text), style: TextStyle(color: AppColors.of(context).text),
), ),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column( child: Column(
children: [ children: [
// choose export method // choose export method
Column( Column(
children: [ children: [
SplittedPanel( SplittedPanel(
title: Text('export_method'.i18n), title: Text('export_method'.i18n),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(4.0), cardPadding: const EdgeInsets.all(4.0),
isSeparated: true, isSeparated: true,
children: [ children: [
PanelButton( PanelButton(
onPressed: () async { onPressed: () async {
// get all grades // get all grades
List<Grade> grades = Provider.of<GradeProvider>( List<Grade> grades = Provider.of<GradeProvider>(
context, context,
listen: false) listen: false)
.grades; .grades;
// gmake a list of grades in json format // gmake a list of grades in json format
List<Map<dynamic, dynamic>> gradesList = [ List<Map<dynamic, dynamic>> gradesList = [
for (Grade grade in grades) for (Grade grade in grades)
// { // {
// '"subject"': '"${grade.subject.name}"', // '"subject"': '"${grade.subject.name}"',
// '"value"': grade.value.value, // '"value"': grade.value.value,
// '"value_name"': // '"value_name"':
// '"${grade.value.valueName}"', // '"${grade.value.valueName}"',
// '"date"': // '"date"':
// '"${grade.date.toIso8601String()}"', // '"${grade.date.toIso8601String()}"',
// '"weight"': grade.value.weight, // '"weight"': grade.value.weight,
// '"type"': '"${grade.type.name}"', // '"type"': '"${grade.type.name}"',
// '"description"': '"${grade.description}"', // '"description"': '"${grade.description}"',
// '"teacher"': '"${grade.teacher.name}"', // '"teacher"': '"${grade.teacher.name}"',
// } // }
grade.json ?? {}, grade.json ?? {},
]; ];
// convert list to json file // convert list to json file
final directory = await getTemporaryDirectory(); final directory = await getTemporaryDirectory();
File file = File('${directory.path}/grades.json'); File file = File('${directory.path}/grades.json');
file.writeAsStringSync( file.writeAsStringSync(
jsonEncode(gradesList), jsonEncode(gradesList),
); );
// convert json to bytes // convert json to bytes
final jsonBytes = file.readAsBytesSync(); final jsonBytes = file.readAsBytesSync();
// get current study year // get current study year
final now = DateTime.now(); final now = DateTime.now();
String studyYearStr = ''; String studyYearStr = '';
if (now.month <= 8) { if (now.month <= 8) {
studyYearStr = '${now.year - 1}_${now.year}'; studyYearStr = '${now.year - 1}_${now.year}';
} else { } else {
studyYearStr = '${now.year}_${now.year + 1}'; studyYearStr = '${now.year}_${now.year + 1}';
} }
// open the share popup with the json file // open the share popup with the json file
Share.shareXFiles( Share.shareXFiles(
[ [
XFile.fromData( XFile.fromData(
jsonBytes, jsonBytes,
name: 'refilc_grades_$studyYearStr', name: 'refilc_grades_$studyYearStr',
mimeType: 'application/json', mimeType: 'application/json',
), ),
], ],
subject: subject:
'reFilc Jegyek - ${studyYearStr.replaceAll('_', '/')}', 'reFilc Jegyek - ${studyYearStr.replaceAll('_', '/')}',
); );
}, },
title: Text( title: Text(
'JSON', 'JSON',
style: TextStyle( style: TextStyle(
color: color:
AppColors.of(context).text.withOpacity(.95), AppColors.of(context).text.withOpacity(.95),
), ),
), ),
// leading: Image.asset( // leading: Image.asset(
// 'assets/images/ext_logo/google.png', // 'assets/images/ext_logo/google.png',
// width: 24.0, // width: 24.0,
// height: 24.0, // height: 24.0,
// ), // ),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12), top: Radius.circular(12),
bottom: Radius.circular(12), bottom: Radius.circular(12),
), ),
), ),
], ],
), ),
// const SizedBox( // const SizedBox(
// height: 10.0, // height: 10.0,
// ), // ),
// const Text( // const Text(
// "Az exportált jegyek jelenleg még nem megtekinthetők a reFilc-ben, csak te magad tudod átnézni őket JSON formátumban. A jövőben ez a funkció bővülni fog, és a jegyeket meg is tekintheted majd a reFilc felületén."), // "Az exportált jegyek jelenleg még nem megtekinthetők a reFilc-ben, csak te magad tudod átnézni őket JSON formátumban. A jövőben ez a funkció bővülni fog, és a jegyeket meg is tekintheted majd a reFilc felületén."),
], ],
), ),
], ],
), ),
), ),
), ),
), ),
), ),
); );
} }
List<Widget> getCalendarList() { List<Widget> getCalendarList() {
// List<Widget> widgets = thirdPartyProvider.googleCalendars // List<Widget> widgets = thirdPartyProvider.googleCalendars
// .map( // .map(
// (e) => Container( // (e) => Container(
// margin: const EdgeInsets.only(bottom: 3.0), // margin: const EdgeInsets.only(bottom: 3.0),
// decoration: BoxDecoration( // decoration: BoxDecoration(
// border: Border.all( // border: Border.all(
// color: Theme.of(context).colorScheme.primary.withOpacity(.25), // color: Theme.of(context).colorScheme.primary.withOpacity(.25),
// width: 1.0, // width: 1.0,
// ), // ),
// borderRadius: BorderRadius.circular(12.0), // borderRadius: BorderRadius.circular(12.0),
// ), // ),
// child: PanelButton( // child: PanelButton(
// onPressed: () async { // onPressed: () async {
// print((e.backgroundColor ?? '#000000').replaceAll('#', '0x')); // print((e.backgroundColor ?? '#000000').replaceAll('#', '0x'));
// setState(() {}); // setState(() {});
// }, // },
// title: Text( // title: Text(
// e.summary ?? 'no_title'.i18n, // e.summary ?? 'no_title'.i18n,
// style: TextStyle( // style: TextStyle(
// color: AppColors.of(context).text.withOpacity(.95), // color: AppColors.of(context).text.withOpacity(.95),
// ), // ),
// ), // ),
// leading: Dot( // leading: Dot(
// color: colorFromHex( // color: colorFromHex(
// e.backgroundColor ?? '#000', // e.backgroundColor ?? '#000',
// ) ?? // ) ??
// Colors.black, // Colors.black,
// ), // ),
// borderRadius: const BorderRadius.vertical( // borderRadius: const BorderRadius.vertical(
// top: Radius.circular(12), // top: Radius.circular(12),
// bottom: Radius.circular(12), // bottom: Radius.circular(12),
// ), // ),
// ), // ),
// ), // ),
// ) // )
// .toList(); // .toList();
List<Widget> widgets = []; List<Widget> widgets = [];
widgets.add( widgets.add(
Container( Container(
margin: const EdgeInsets.only(bottom: 3.0), margin: const EdgeInsets.only(bottom: 3.0),
decoration: BoxDecoration( decoration: BoxDecoration(
// border: Border.all( // border: Border.all(
// color: Theme.of(context).colorScheme.primary.withOpacity(.25), // color: Theme.of(context).colorScheme.primary.withOpacity(.25),
// width: 1.0, // width: 1.0,
// ), // ),
color: AppColors.of(context).highlight, color: AppColors.of(context).highlight,
borderRadius: BorderRadius.circular(16.0), borderRadius: BorderRadius.circular(16.0),
), ),
child: PanelButton( child: PanelButton(
onPressed: null, onPressed: null,
// onPressed: () async { // onPressed: () async {
// // thirdPartyProvider.pushTimetable(context, timetable); // // thirdPartyProvider.pushTimetable(context, timetable);
// setState(() {}); // setState(() {});
// }, // },
title: Text( title: Text(
'reFilc - Órarend', 'reFilc - Órarend',
style: TextStyle( style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95), color: AppColors.of(context).text.withOpacity(.95),
), ),
), ),
// leading: Icon( // leading: Icon(
// FeatherIcons.plus, // FeatherIcons.plus,
// size: 20.0, // size: 20.0,
// color: AppColors.of(context).text.withOpacity(0.75), // color: AppColors.of(context).text.withOpacity(0.75),
// ), // ),
leading: Dot( leading: Dot(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12), top: Radius.circular(12),
bottom: Radius.circular(12), bottom: Radius.circular(12),
), ),
), ),
), ),
); );
return widgets; return widgets;
} }
} }

View File

@ -1,157 +1,157 @@
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension/i18n_extension.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class WelcomeMessagePanelButton extends StatelessWidget { class WelcomeMessagePanelButton extends StatelessWidget {
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
late UserProvider user; late UserProvider user;
WelcomeMessagePanelButton(this.settingsProvider, this.user, {super.key}); WelcomeMessagePanelButton(this.settingsProvider, this.user, {super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String finalName = ((user.nickname ?? '') != '' String finalName = ((user.nickname ?? '') != ''
? user.nickname ? user.nickname
: (user.displayName ?? '') != '' : (user.displayName ?? '') != ''
? user.displayName ? user.displayName
: 'János') ?? : 'János') ??
'János'; 'János';
return PanelButton( return PanelButton(
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
builder: (context) => WelcomeMessageEditor(settingsProvider)); builder: (context) => WelcomeMessageEditor(settingsProvider));
}, },
title: Text( title: Text(
"welcome_msg".i18n, "welcome_msg".i18n,
style: TextStyle( style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95), color: AppColors.of(context).text.withOpacity(.95),
), ),
), ),
leading: Icon( leading: Icon(
FeatherIcons.smile, FeatherIcons.smile,
size: 22.0, size: 22.0,
color: AppColors.of(context).text.withOpacity(.95), color: AppColors.of(context).text.withOpacity(.95),
), ),
trailing: Container( trailing: Container(
constraints: const BoxConstraints(maxWidth: 100), constraints: const BoxConstraints(maxWidth: 100),
child: Text( child: Text(
settingsProvider.welcomeMessage.replaceAll(' ', '') != '' settingsProvider.welcomeMessage.replaceAll(' ', '') != ''
? localizeFill( ? localizeFill(
settingsProvider.welcomeMessage, settingsProvider.welcomeMessage,
[finalName], [finalName],
) )
: 'default'.i18n, : 'default'.i18n,
style: const TextStyle(fontSize: 14.0), style: const TextStyle(fontSize: 14.0),
textAlign: TextAlign.end, textAlign: TextAlign.end,
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
); );
} }
} }
// ignore: must_be_immutable // ignore: must_be_immutable
class WelcomeMessageEditor extends StatefulWidget { class WelcomeMessageEditor extends StatefulWidget {
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
WelcomeMessageEditor(this.settingsProvider, {super.key}); WelcomeMessageEditor(this.settingsProvider, {super.key});
@override @override
State<WelcomeMessageEditor> createState() => _WelcomeMessageEditorState(); State<WelcomeMessageEditor> createState() => _WelcomeMessageEditorState();
} }
class _WelcomeMessageEditorState extends State<WelcomeMessageEditor> { class _WelcomeMessageEditorState extends State<WelcomeMessageEditor> {
final _welcomeMsg = TextEditingController(); final _welcomeMsg = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_welcomeMsg.text = _welcomeMsg.text =
widget.settingsProvider.welcomeMessage.replaceAll('%s', '%name%'); widget.settingsProvider.welcomeMessage.replaceAll('%s', '%name%');
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text("edit_welcome_msg".i18n), title: Text("edit_welcome_msg".i18n),
content: TextField( content: TextField(
controller: _welcomeMsg, controller: _welcomeMsg,
autofocus: true, autofocus: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
label: Text('welcome_msg'.i18n), label: Text('welcome_msg'.i18n),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(FeatherIcons.x), icon: const Icon(FeatherIcons.x),
onPressed: () { onPressed: () {
setState(() { setState(() {
_welcomeMsg.text = ""; _welcomeMsg.text = "";
}); });
}, },
), ),
), ),
), ),
actions: [ actions: [
TextButton( TextButton(
child: Text( child: Text(
"cancel".i18n, "cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
onPressed: () { onPressed: () {
Navigator.of(context).maybePop(); Navigator.of(context).maybePop();
}, },
), ),
TextButton( TextButton(
child: Text( child: Text(
"done".i18n, "done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
onPressed: () { onPressed: () {
// var trimmed = _welcomeMsg.text.trim(); // var trimmed = _welcomeMsg.text.trim();
// var defLen = trimmed.length; // var defLen = trimmed.length;
// var replacedLen = trimmed.replaceAll('%s', '').length; // var replacedLen = trimmed.replaceAll('%s', '').length;
// if (defLen - 2 > replacedLen) { // if (defLen - 2 > replacedLen) {
// print('fuck yourself rn'); // print('fuck yourself rn');
// } // }
var finalText = _welcomeMsg.text var finalText = _welcomeMsg.text
.trim() .trim()
.replaceFirst('%name%', '\$s') .replaceFirst('%name%', '\$s')
.replaceFirst('%user%', '\$s') .replaceFirst('%user%', '\$s')
.replaceFirst('%username%', '\$s') .replaceFirst('%username%', '\$s')
.replaceFirst('%me%', '\$s') .replaceFirst('%me%', '\$s')
.replaceFirst('%profile%', '\$s') .replaceFirst('%profile%', '\$s')
.replaceAll('%', '') .replaceAll('%', '')
.replaceFirst('\$s', '%s'); .replaceFirst('\$s', '%s');
// .replaceAll('\$s', 's'); // .replaceAll('\$s', 's');
if (!Provider.of<PlusProvider>(context, listen: false) if (!Provider.of<PlusProvider>(context, listen: false)
.hasScope(PremiumScopes.welcomeMessage) && .hasScope(PremiumScopes.welcomeMessage) &&
finalText.replaceAll(' ', '') != '') { finalText.replaceAll(' ', '') != '') {
PlusLockedFeaturePopup.show( PlusLockedFeaturePopup.show(
context: context, feature: PremiumFeature.welcomeMessage); context: context, feature: PremiumFeature.welcomeMessage);
return; return;
} }
widget.settingsProvider widget.settingsProvider
.update(welcomeMessage: finalText, store: true); .update(welcomeMessage: finalText, store: true);
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
), ),
], ],
); );
} }
} }

View File

@ -1,52 +1,52 @@
name: refilc_plus name: refilc_plus
publish_to: "none" publish_to: "none"
environment: environment:
sdk: ^3.6.0 sdk: ^3.6.0
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
# reFilc Main # reFilc Main
refilc: refilc:
path: ../refilc/ path: ../refilc/
# e-KRETA API (kreten) client # e-KRETA API (kreten) client
refilc_kreta_api: refilc_kreta_api:
path: ../refilc_kreta_api/ path: ../refilc_kreta_api/
# reFilc Mobile UI # reFilc Mobile UI
refilc_mobile_ui: refilc_mobile_ui:
path: "../refilc_mobile_ui/" path: "../refilc_mobile_ui/"
provider: ^6.1.1 provider: ^6.1.1
flutter_feather_icons: ^2.0.0+1 flutter_feather_icons: ^2.0.0+1
uni_links: ^0.5.1 uni_links: ^0.5.1
url_launcher: ^6.2.5 url_launcher: ^6.2.5
dropdown_button2: ^2.3.9 dropdown_button2: ^2.3.9
home_widget: home_widget:
git: git:
url: https://github.com/refilc/home_widget.git url: https://github.com/refilc/home_widget.git
ref: flutter-beta ref: flutter-beta
image_picker: ^1.0.7 image_picker: ^1.0.7
image_crop: image_crop:
git: git:
url: https://github.com/kimaah/image_crop.git url: https://github.com/kimaah/image_crop.git
lottie: ^3.1.0 lottie: ^3.1.0
animations: ^2.0.11 animations: ^2.0.11
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
flutter_dynamic_icon: ^2.1.0 flutter_dynamic_icon: ^2.1.0
android_dynamic_icon: ^2.0.0 # android_dynamic_icon: ^2.0.0
i18n_extension: ^12.0.1 i18n_extension: ^12.0.1
http: ^1.2.0 http: ^1.2.0
fl_chart: ^0.68.0 fl_chart: ^0.68.0
flutter_dynamic_icon_plus: ^1.1.2 flutter_dynamic_icon_plus: ^1.1.2
share_plus: ^10.0.3 share_plus: ^10.0.3
path_provider: ^2.1.3 path_provider: ^2.1.3
file_picker: ^8.0.5 file_picker: ^8.0.5
dev_dependencies: dev_dependencies:
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true