From 3c431cbce11a132738235da580c6f5aeef1fafa5 Mon Sep 17 00:00:00 2001 From: unknown <55922348+55nknown@users.noreply.github.com> Date: Mon, 2 May 2022 22:07:06 +0200 Subject: [PATCH] Livecardrework (#104) --- .gitignore | 2 + changelog.md | 7 + filcnaplo/assets/fonts/FilcIcons.ttf | Bin 2164 -> 2316 bytes filcnaplo/assets/icons/home.svg | 5 - filcnaplo/assets/icons/linux.svg | 3 - .../lib/api/providers/user_provider.dart | 2 +- filcnaplo/lib/app.dart | 86 +++++----- filcnaplo/lib/database/init.dart | 112 ++++++------- filcnaplo/lib/database/query.dart | 28 +++- filcnaplo/lib/database/store.dart | 29 +++- filcnaplo/lib/database/struct.dart | 3 +- filcnaplo/lib/helpers/subject_icon.dart | 2 +- filcnaplo/lib/helpers/update_helper.dart | 2 +- filcnaplo/lib/icons/filc_icons.dart | 19 ++- filcnaplo/lib/main.dart | 2 +- filcnaplo/lib/models/release.dart | 2 +- filcnaplo/lib/models/settings.dart | 53 ++++++- .../lib/models/subject_lesson_count.dart | 31 ++++ filcnaplo/lib/theme.dart | 150 +++++++++++++----- filcnaplo/lib/utils/reverse_search.dart | 37 +++++ filcnaplo/pubspec.yaml | 12 +- filcnaplo_kreta_api | 2 +- filcnaplo_mobile_ui | 2 +- 23 files changed, 415 insertions(+), 176 deletions(-) delete mode 100644 filcnaplo/assets/icons/home.svg delete mode 100644 filcnaplo/assets/icons/linux.svg create mode 100644 filcnaplo/lib/models/subject_lesson_count.dart create mode 100644 filcnaplo/lib/utils/reverse_search.dart diff --git a/.gitignore b/.gitignore index 5cbfa52..4b56298 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # See https://www.dartlang.org/guides/libraries/private-files +termek.txt + # Files and directories created by pub .dart_tool/ .packages diff --git a/changelog.md b/changelog.md index 429f8ca..e01a32c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +- Újra dizájnolt jegy, stb. nézet +- Offline tárolt osztályátlagok +- Csengő késésének beállítása +- Óra végén a hátralévő idő másodperc pontos kijelzése +- Vissza gombra főoldalra ugrás +- Eddigi hiányzott órák százalékos kijelzése +- Hiányzások tantárgy szerinti rendezése - Lapozás legyintéssel a tabok között - Hiányzások jelzése az órarenben - Fordítás javítások diff --git a/filcnaplo/assets/fonts/FilcIcons.ttf b/filcnaplo/assets/fonts/FilcIcons.ttf index d1e782b88fbb1306f78ff93f8e0e6fe6aa8fd5cf..9a3f33a32ef2c37b43045e09ac41b2cc55199ed7 100644 GIT binary patch literal 2316 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 TzkAUbFx}1G`^J{r?%cls9mW>- diff --git a/filcnaplo/assets/icons/home.svg b/filcnaplo/assets/icons/home.svg deleted file mode 100644 index a536f40..0000000 --- a/filcnaplo/assets/icons/home.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/filcnaplo/assets/icons/linux.svg b/filcnaplo/assets/icons/linux.svg deleted file mode 100644 index 38430b8..0000000 --- a/filcnaplo/assets/icons/linux.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/filcnaplo/lib/api/providers/user_provider.dart b/filcnaplo/lib/api/providers/user_provider.dart index fdd313f..5de7396 100644 --- a/filcnaplo/lib/api/providers/user_provider.dart +++ b/filcnaplo/lib/api/providers/user_provider.dart @@ -24,7 +24,7 @@ class UserProvider with ChangeNotifier { void addUser(User user) { _users[user.id] = user; if (kDebugMode) { - print("DEBUG: Added User: ${user.id} ${user.name}"); + print("DEBUG: Added User: ${user.id}"); } } diff --git a/filcnaplo/lib/app.dart b/filcnaplo/lib/app.dart index d94bd2f..3f1421e 100644 --- a/filcnaplo/lib/app.dart +++ b/filcnaplo/lib/app.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:math'; +import 'package:dynamic_color/dynamic_color.dart'; import 'package:filcnaplo/api/client.dart'; import 'package:filcnaplo/api/providers/news_provider.dart'; import 'package:filcnaplo/api/providers/database_provider.dart'; @@ -18,6 +19,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:i18n_extension/i18n_widget.dart'; +import 'package:material_color_utilities/palettes/core_palette.dart'; import 'package:provider/provider.dart'; // Providers @@ -51,12 +53,14 @@ class App extends StatelessWidget { // Set high refresh mode #28 if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate(); - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { FilcAPI.getConfig(settings).then((Config? config) { if (config != null) settings.update(context, database: database, config: config); }); }); + CorePalette? corePalette; + return I18n( initialLocale: Locale(settings.language, settings.language.toUpperCase()), child: MultiProvider( @@ -84,45 +88,55 @@ class App extends StatelessWidget { ], child: Consumer( builder: (context, themeMode, child) { - return MaterialApp( - builder: (context, child) { - // Limit font size scaling to 1.0 - double textScaleFactor = min(MediaQuery.of(context).textScaleFactor, 1.0); + return FutureBuilder( + future: DynamicColorPlugin.getCorePalette(), + builder: (context, snapshot) { + corePalette = snapshot.data; + return MaterialApp( + builder: (context, child) { + // Limit font size scaling to 1.0 + double textScaleFactor = min(MediaQuery.of(context).textScaleFactor, 1.0); - return MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor), - child: child ?? Container(), - ); - }, - title: "Filc Napló", - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme(context), - darkTheme: AppTheme.darkTheme(context), - themeMode: themeMode.themeMode, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', 'EN'), - Locale('hu', 'HU'), - Locale('de', 'DE'), - ], - localeListResolutionCallback: (locales, supported) { - Locale locale = const Locale('hu', 'HU'); + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor), + child: SafeArea( + top: false, + child: child ?? Container(), + ), + ); + }, + title: "Filc Napló", + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme(context, palette: corePalette), + darkTheme: AppTheme.darkTheme(context, palette: corePalette), + themeMode: themeMode.themeMode, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', 'EN'), + Locale('hu', 'HU'), + Locale('de', 'DE'), + ], + localeListResolutionCallback: (locales, supported) { + Locale locale = const Locale('hu', 'HU'); - for (var loc in locales ?? []) { - if (supported.contains(loc)) { - locale = loc; - break; + for (var loc in locales ?? []) { + if (supported.contains(loc)) { + locale = loc; + break; + } } - } - return locale; - }, - onGenerateRoute: (settings) => rootNavigator(settings), - initialRoute: user.getUsers().isNotEmpty ? "navigation" : "login"); + return locale; + }, + onGenerateRoute: (settings) => rootNavigator(settings), + initialRoute: user.getUsers().isNotEmpty ? "navigation" : "login", + ); + }, + ); }, ), ), diff --git a/filcnaplo/lib/database/init.dart b/filcnaplo/lib/database/init.dart index 4f38412..b16c0fd 100644 --- a/filcnaplo/lib/database/init.dart +++ b/filcnaplo/lib/database/init.dart @@ -7,6 +7,24 @@ import 'package:filcnaplo/models/settings.dart'; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +const settingsDB = DatabaseStruct("settings", { + "language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int, + "update_channel": int, "config": String, // general + "grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors + "vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int, + "notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications + "x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int, +}); +const usersDB = DatabaseStruct( + "users", {"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int}); +const userDataDB = DatabaseStruct("user_data", { + "id": String, "grades": String, "timetable": String, "exams": String, "homework": String, "messages": String, "notes": String, + "events": String, "absences": String, "group_averages": String, + // "subject_lesson_count": String, // non kreta data +}); + +Future createTable(Database db, DatabaseStruct struct) => db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)"); + Future initDB() async { Database db; @@ -17,13 +35,9 @@ Future initDB() async { db = await openDatabase("app.db"); } - // Create table Users - var usersDB = await createUsersTable(db); - await db.execute("CREATE TABLE IF NOT EXISTS user_data (" - "id TEXT NOT NULL, grades TEXT, timetable TEXT, exams TEXT, homework TEXT, messages TEXT, notes TEXT, events TEXT, absences TEXT)"); - - // Create table Settings - var settingsDB = await createSettingsTable(db); + await createTable(db, settingsDB); + await createTable(db, usersDB); + await createTable(db, userDataDB); if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) { // Set default values for table Settings @@ -32,8 +46,21 @@ Future initDB() async { // Migrate Databases try { - await migrateDB(db, "settings", settingsDB.struct.keys, SettingsProvider.defaultSettings().toMap(), createSettingsTable); - await migrateDB(db, "users", usersDB.struct.keys, {"role": 0}, createUsersTable); + await migrateDB( + db, + struct: settingsDB, + defaultValues: SettingsProvider.defaultSettings().toMap(), + ); + await migrateDB( + db, + struct: usersDB, + defaultValues: {"role": 0}, + ); + await migrateDB(db, struct: userDataDB, defaultValues: { + "grades": "[]", "timetable": "[]", "exams": "[]", "homework": "[]", "messages": "[]", "notes": "[]", "events": "[]", "absences": "[]", + "group_averages": "[]", + // "subject_lesson_count": "{}", // non kreta data + }); } catch (error) { print("ERROR: migrateDB: $error"); } @@ -41,44 +68,16 @@ Future initDB() async { return db; } -Future createSettingsTable(Database db) async { - var settingsDB = DatabaseStruct({ - "language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int, - "update_channel": int, "config": String, // general - "grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors - "vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int, - "notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications - "x_filc_id": String, "graph_class_avg": int, - }); - - // Create table Settings - await db.execute("CREATE TABLE IF NOT EXISTS settings ($settingsDB)"); - - return settingsDB; -} - -Future createUsersTable(Database db) async { - var usersDB = DatabaseStruct( - {"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int}); - - // Create table Users - await db.execute("CREATE TABLE IF NOT EXISTS users ($usersDB)"); - - return usersDB; -} - Future migrateDB( - Database db, - String table, - Iterable keys, - Map defaultValues, - Future Function(Database) create, -) async { - var originalRows = await db.query(table); + Database db, { + required DatabaseStruct struct, + required Map defaultValues, +}) async { + var originalRows = await db.query(struct.table); if (originalRows.isEmpty) { - await db.execute("drop table $table"); - await create(db); + await db.execute("drop table ${struct.table}"); + await createTable(db, struct); return; } @@ -86,25 +85,28 @@ Future migrateDB( // go through each row and add missing keys or delete non existing keys await Future.forEach>(originalRows, (original) async { - bool migrationRequired = keys.any((key) => !original.containsKey(key) || original[key] == null); + bool migrationRequired = struct.struct.keys.any((key) => !original.containsKey(key) || original[key] == null) || + original.keys.any((key) => !struct.struct.containsKey(key)); if (migrationRequired) { - print("INFO: Migrating $table"); + print("INFO: Migrating ${struct.table}"); var copy = Map.from(original); // Fill missing columns - for (var key in keys) { - if (!keys.contains(key)) { - print("DEBUG: dropping $key"); - copy.remove(key); - } - + for (var key in struct.struct.keys) { if (!original.containsKey(key) || original[key] == null) { print("DEBUG: migrating $key"); copy[key] = defaultValues[key]; } } + for (var key in original.keys) { + if (!struct.struct.keys.contains(key)) { + print("DEBUG: dropping $key"); + copy.remove(key); + } + } + migrated.add(copy); } }); @@ -112,12 +114,12 @@ Future migrateDB( // replace the old table with the migrated one if (migrated.isNotEmpty) { // Delete table - await db.execute("drop table $table"); + await db.execute("drop table ${struct.table}"); // Recreate table - await create(db); + await createTable(db, struct); await Future.forEach(migrated, (Map copy) async { - await db.insert(table, copy); + await db.insert(struct.table, copy); }); print("INFO: Database migrated"); diff --git a/filcnaplo/lib/database/query.dart b/filcnaplo/lib/database/query.dart index 8ffef72..b5de940 100644 --- a/filcnaplo/lib/database/query.dart +++ b/filcnaplo/lib/database/query.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:filcnaplo/models/subject_lesson_count.dart'; import 'package:filcnaplo/models/user.dart'; import 'package:sqflite_common/sqlite_api.dart'; @@ -13,6 +14,7 @@ import 'package:filcnaplo_kreta_api/models/message.dart'; import 'package:filcnaplo_kreta_api/models/note.dart'; import 'package:filcnaplo_kreta_api/models/event.dart'; import 'package:filcnaplo_kreta_api/models/absence.dart'; +import 'package:filcnaplo_kreta_api/models/group_average.dart'; class DatabaseQuery { DatabaseQuery({required this.db}); @@ -106,9 +108,27 @@ class UserDatabaseQuery { Future> getAbsences({required String userId}) async { List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); if (userData.isEmpty) return []; - String? absebcesJson = userData.elementAt(0)["absences"] as String?; - if (absebcesJson == null) return []; - List absebces = (jsonDecode(absebcesJson) as List).map((e) => Absence.fromJson(e)).toList(); - return absebces; + String? absencesJson = userData.elementAt(0)["absences"] as String?; + if (absencesJson == null) return []; + List absences = (jsonDecode(absencesJson) as List).map((e) => Absence.fromJson(e)).toList(); + return absences; + } + + Future> getGroupAverages({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return []; + String? groupAveragesJson = userData.elementAt(0)["group_averages"] as String?; + if (groupAveragesJson == null) return []; + List groupAverages = (jsonDecode(groupAveragesJson) as List).map((e) => GroupAverage.fromJson(e)).toList(); + return groupAverages; + } + + Future getSubjectLessonCount({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return SubjectLessonCount.fromMap({}); + String? lessonCountJson = userData.elementAt(0)["subject_lesson_count"] as String?; + if (lessonCountJson == null) return SubjectLessonCount.fromMap({}); + SubjectLessonCount lessonCount = SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map); + return lessonCount; } } diff --git a/filcnaplo/lib/database/store.dart b/filcnaplo/lib/database/store.dart index 2b93002..c442909 100644 --- a/filcnaplo/lib/database/store.dart +++ b/filcnaplo/lib/database/store.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:filcnaplo/models/subject_lesson_count.dart'; import 'package:sqflite_common/sqlite_api.dart'; // Models @@ -12,6 +13,7 @@ import 'package:filcnaplo_kreta_api/models/message.dart'; import 'package:filcnaplo_kreta_api/models/note.dart'; import 'package:filcnaplo_kreta_api/models/event.dart'; import 'package:filcnaplo_kreta_api/models/absence.dart'; +import 'package:filcnaplo_kreta_api/models/group_average.dart'; class DatabaseStore { DatabaseStore({required this.db}); @@ -43,43 +45,54 @@ class UserDatabaseStore { final Database db; - Future storeGrades(List grades, {required String userId}) async { + Future storeGrades(List grades, {required String userId}) async { String gradesJson = jsonEncode(grades.map((e) => e.json).toList()); await db.update("user_data", {"grades": gradesJson}, where: "id = ?", whereArgs: [userId]); } - Future storeLessons(List lessons, {required String userId}) async { + Future storeLessons(List lessons, {required String userId}) async { String lessonsJson = jsonEncode(lessons.map((e) => e.json).toList()); await db.update("user_data", {"timetable": lessonsJson}, where: "id = ?", whereArgs: [userId]); } - Future storeExams(List exams, {required String userId}) async { + Future storeExams(List exams, {required String userId}) async { String examsJson = jsonEncode(exams.map((e) => e.json).toList()); await db.update("user_data", {"exams": examsJson}, where: "id = ?", whereArgs: [userId]); } - Future storeHomework(List homework, {required String userId}) async { + Future storeHomework(List homework, {required String userId}) async { String homeworkJson = jsonEncode(homework.map((e) => e.json).toList()); await db.update("user_data", {"homework": homeworkJson}, where: "id = ?", whereArgs: [userId]); } - Future storeMessages(List messages, {required String userId}) async { + Future storeMessages(List messages, {required String userId}) async { String messagesJson = jsonEncode(messages.map((e) => e.json).toList()); await db.update("user_data", {"messages": messagesJson}, where: "id = ?", whereArgs: [userId]); } - Future storeNotes(List notes, {required String userId}) async { + Future storeNotes(List notes, {required String userId}) async { String notesJson = jsonEncode(notes.map((e) => e.json).toList()); await db.update("user_data", {"notes": notesJson}, where: "id = ?", whereArgs: [userId]); } - Future storeEvents(List events, {required String userId}) async { + Future storeEvents(List events, {required String userId}) async { String eventsJson = jsonEncode(events.map((e) => e.json).toList()); await db.update("user_data", {"events": eventsJson}, where: "id = ?", whereArgs: [userId]); } - Future storeAbsences(List absences, {required String userId}) async { + Future storeAbsences(List absences, {required String userId}) async { String absencesJson = jsonEncode(absences.map((e) => e.json).toList()); await db.update("user_data", {"absences": absencesJson}, where: "id = ?", whereArgs: [userId]); } + + Future storeGroupAverages(List groupAverages, {required String userId}) async { + String groupAveragesJson = jsonEncode(groupAverages.map((e) => e.json).toList()); + await db.update("user_data", {"group_averages": groupAveragesJson}, where: "id = ?", whereArgs: [userId]); + } + + + Future storeSubjectLessonCount(SubjectLessonCount lessonCount, {required String userId}) async { + String lessonCountJson = jsonEncode(lessonCount.toMap()); + await db.update("user_data", {"subject_lesson_count": lessonCountJson}, where: "id = ?", whereArgs: [userId]); + } } diff --git a/filcnaplo/lib/database/struct.dart b/filcnaplo/lib/database/struct.dart index a84a673..bb33de6 100644 --- a/filcnaplo/lib/database/struct.dart +++ b/filcnaplo/lib/database/struct.dart @@ -1,7 +1,8 @@ class DatabaseStruct { + final String table; final Map struct; - DatabaseStruct(this.struct); + const DatabaseStruct(this.table, this.struct); String _toDBfield(String name, dynamic type) { String typeName = ""; diff --git a/filcnaplo/lib/helpers/subject_icon.dart b/filcnaplo/lib/helpers/subject_icon.dart index 6bbf88d..4f81cbb 100644 --- a/filcnaplo/lib/helpers/subject_icon.dart +++ b/filcnaplo/lib/helpers/subject_icon.dart @@ -35,7 +35,7 @@ class SubjectIcon { if (RegExp("technika").hasMatch(name)) return Icons.build_outlined; if (RegExp("tanc").hasMatch(name)) return Icons.speaker_outlined; if (RegExp("filozofia").hasMatch(name)) return Icons.psychology_outlined; - if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name)) return Icons.groups_outlined; + if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name) || name == "ofo") return Icons.groups_outlined; if (RegExp("gazdasag").hasMatch(name)) return Icons.account_balance_outlined; if (RegExp("szorgalom").hasMatch(name)) return Icons.verified_outlined; if (RegExp("magatartas").hasMatch(name)) return Icons.emoji_people_outlined; diff --git a/filcnaplo/lib/helpers/update_helper.dart b/filcnaplo/lib/helpers/update_helper.dart index ef0124a..2d6f15c 100644 --- a/filcnaplo/lib/helpers/update_helper.dart +++ b/filcnaplo/lib/helpers/update_helper.dart @@ -32,7 +32,7 @@ extension UpdateHelper on Release { if (result.type != ResultType.done) { // ignore: avoid_print - print("ERROR: installUpdate.openFile: " + result.message); + print("ERROR: installUpdate.openFile: ${result.message}"); throw result.message; } diff --git a/filcnaplo/lib/icons/filc_icons.dart b/filcnaplo/lib/icons/filc_icons.dart index a1fb495..e0e1458 100644 --- a/filcnaplo/lib/icons/filc_icons.dart +++ b/filcnaplo/lib/icons/filc_icons.dart @@ -1,10 +1,19 @@ import 'package:flutter/widgets.dart'; class FilcIcons { - static const IconData home = FilcIconData(0x41); - static const IconData linux = FilcIconData(0x42); -} + FilcIcons._(); -class FilcIconData extends IconData { - const FilcIconData(int codePoint) : super(codePoint, fontFamily: "FilcIcons"); + static const iconFontFamily = 'FilcIcons'; + + /// home + static const IconData home = IconData(0x00, fontFamily: iconFontFamily); + + /// linux + static const IconData linux = IconData(0x01, fontFamily: iconFontFamily); + + /// upstairs + static const IconData upstairs = IconData(0x02, fontFamily: iconFontFamily); + + /// downstairs + static const IconData downstairs = IconData(0x03, fontFamily: iconFontFamily); } diff --git a/filcnaplo/lib/main.dart b/filcnaplo/lib/main.dart index 48518ee..918eab0 100644 --- a/filcnaplo/lib/main.dart +++ b/filcnaplo/lib/main.dart @@ -48,7 +48,7 @@ Widget errorBuilder(FlutterErrorDetails details) { return Builder(builder: (context) { if (Navigator.of(context).canPop()) Navigator.pop(context); - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { if (!errorShown && details.exceptionAsString() != lastException) { errorShown = true; lastException = details.exceptionAsString(); diff --git a/filcnaplo/lib/models/release.dart b/filcnaplo/lib/models/release.dart index 5c07248..6d1696d 100644 --- a/filcnaplo/lib/models/release.dart +++ b/filcnaplo/lib/models/release.dart @@ -127,7 +127,7 @@ class Version { } static const zero = Version(0, 0, 0); - static const List prereleases = ["dev", "pre", "alpha", "beta", "rc"]; + static const List prereleases = ["dev", "pre", "alpha", "beta", "rc", "nightly", "test"]; @override int get hashCode => toString().hashCode; diff --git a/filcnaplo/lib/models/settings.dart b/filcnaplo/lib/models/settings.dart index 7b81750..7be831f 100644 --- a/filcnaplo/lib/models/settings.dart +++ b/filcnaplo/lib/models/settings.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:filcnaplo/api/providers/database_provider.dart'; import 'package:filcnaplo/models/config.dart'; @@ -8,7 +9,9 @@ import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; enum Pages { home, grades, timetable, messages, absences } + enum UpdateChannel { stable, beta, dev } + enum VibrationStrength { off, light, medium, strong } class SettingsProvider extends ChangeNotifier { @@ -47,6 +50,10 @@ class SettingsProvider extends ChangeNotifier { Config _config; String _xFilcId; bool _graphClassAvg; + bool _goodStudent; + bool _presentationMode; + bool _bellDelayEnabled; + int _bellDelay; SettingsProvider({ required String language, @@ -68,6 +75,10 @@ class SettingsProvider extends ChangeNotifier { required Config config, required String xFilcId, required bool graphClassAvg, + required bool goodStudent, + required bool presentationMode, + required bool bellDelayEnabled, + required int bellDelay, }) : _language = language, _startPage = startPage, _rounding = rounding, @@ -86,9 +97,21 @@ class SettingsProvider extends ChangeNotifier { _updateChannel = updateChannel, _config = config, _xFilcId = xFilcId, - _graphClassAvg = graphClassAvg; + _graphClassAvg = graphClassAvg, + _goodStudent = goodStudent, + _presentationMode = presentationMode, + _bellDelayEnabled = bellDelayEnabled, + _bellDelay = bellDelay; factory SettingsProvider.fromMap(Map map) { + Map? configMap; + + try { + configMap = jsonDecode(map["config"] ?? "{}"); + } catch (e) { + log("[ERROR] SettingsProvider.fromMap: $e"); + } + return SettingsProvider( language: map["language"], startPage: Pages.values[map["start_page"]], @@ -112,9 +135,13 @@ class SettingsProvider extends ChangeNotifier { abWeeks: map["ab_weeks"] == 1, swapABweeks: map["swap_ab_weeks"] == 1, updateChannel: UpdateChannel.values[map["update_channel"]], - config: Config.fromJson(jsonDecode(map["config"] ?? "{}")), + config: Config.fromJson(configMap ?? {}), xFilcId: map["x_filc_id"], graphClassAvg: map["graph_class_avg"] == 1, + goodStudent: false, + presentationMode: map["presentation_mode"] == 1, + bellDelayEnabled: map["bell_delay_enabled"] == 1, + bellDelay: map["bell_delay"], ); } @@ -143,6 +170,9 @@ class SettingsProvider extends ChangeNotifier { "config": jsonEncode(config.json), "x_filc_id": _xFilcId, "graph_class_avg": _graphClassAvg ? 1 : 0, + "presentation_mode": _presentationMode ? 1 : 0, + "bell_delay_enabled": _bellDelayEnabled ? 1 : 0, + "bell_delay": _bellDelay, }; } @@ -173,6 +203,10 @@ class SettingsProvider extends ChangeNotifier { config: Config.fromJson({}), xFilcId: const Uuid().v4(), graphClassAvg: false, + goodStudent: false, + presentationMode: false, + bellDelayEnabled: false, + bellDelay: 0, ); } @@ -196,10 +230,15 @@ class SettingsProvider extends ChangeNotifier { Config get config => _config; String get xFilcId => _xFilcId; bool get graphClassAvg => _graphClassAvg; + bool get goodStudent => _goodStudent; + bool get presentationMode => _presentationMode; + bool get bellDelayEnabled => _bellDelayEnabled; + int get bellDelay => _bellDelay; Future update( BuildContext context, { DatabaseProvider? database, + bool store = true, String? language, Pages? startPage, int? rounding, @@ -219,6 +258,10 @@ class SettingsProvider extends ChangeNotifier { Config? config, String? xFilcId, bool? graphClassAvg, + bool? goodStudent, + bool? presentationMode, + bool? bellDelayEnabled, + int? bellDelay, }) async { if (language != null && language != _language) _language = language; if (startPage != null && startPage != _startPage) _startPage = startPage; @@ -241,9 +284,13 @@ class SettingsProvider extends ChangeNotifier { if (config != null && config != _config) _config = config; if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId; if (graphClassAvg != null && graphClassAvg != _graphClassAvg) _graphClassAvg = graphClassAvg; + if (goodStudent != null) _goodStudent = goodStudent; + if (presentationMode != null && presentationMode != _presentationMode) _presentationMode = presentationMode; + if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay; + if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) _bellDelayEnabled = bellDelayEnabled; database ??= Provider.of(context, listen: false); - await database.store.storeSettings(this); + if (store) await database.store.storeSettings(this); notifyListeners(); } } diff --git a/filcnaplo/lib/models/subject_lesson_count.dart b/filcnaplo/lib/models/subject_lesson_count.dart new file mode 100644 index 0000000..42a0c59 --- /dev/null +++ b/filcnaplo/lib/models/subject_lesson_count.dart @@ -0,0 +1,31 @@ +import 'package:filcnaplo_kreta_api/models/category.dart'; +import 'package:filcnaplo_kreta_api/models/subject.dart'; + +enum SubjectLessonCountUpdateState { ready, updating } + +class SubjectLessonCount { + DateTime lastUpdated; + Map subjects; + SubjectLessonCountUpdateState state; + + SubjectLessonCount({required this.lastUpdated, required this.subjects, this.state = SubjectLessonCountUpdateState.ready}); + + factory SubjectLessonCount.fromMap(Map json) { + return SubjectLessonCount( + lastUpdated: DateTime.fromMillisecondsSinceEpoch(json["last_updated"] ?? 0), + subjects: ((json["subjects"] as Map?) ?? {}).map( + (key, value) => MapEntry( + Subject(id: key, name: "", category: Category.fromJson({})), + value, + ), + ), + ); + } + + Map toMap() { + return { + "last_updated": lastUpdated.millisecondsSinceEpoch, + "subjects": subjects.map((key, value) => MapEntry(key.id, value)), + }; + } +} diff --git a/filcnaplo/lib/theme.dart b/filcnaplo/lib/theme.dart index a06b519..272e406 100644 --- a/filcnaplo/lib/theme.dart +++ b/filcnaplo/lib/theme.dart @@ -1,5 +1,6 @@ import 'package:filcnaplo/models/settings.dart'; import 'package:flutter/material.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; import 'package:provider/provider.dart'; class AppTheme { @@ -8,56 +9,116 @@ class AppTheme { static const String _fontFamily = "Montserrat"; + static Color? _paletteAccentLight(CorePalette? palette) => palette != null ? Color(palette.primary.get(70)) : null; + static Color? _paletteHighlightLight(CorePalette? palette) => palette != null ? Color(palette.neutral.get(100)) : null; + static Color? _paletteBackgroundLight(CorePalette? palette) => palette != null ? Color(palette.neutral.get(95)) : null; + + static Color? _paletteAccentDark(CorePalette? palette) => palette != null ? Color(palette.primary.get(80)) : null; + static Color? _paletteBackgroundDark(CorePalette? palette) => palette != null ? Color(palette.neutralVariant.get(10)) : null; + static Color? _paletteHighlightDark(CorePalette? palette) => palette != null ? Color(palette.neutralVariant.get(20)) : null; + // Light Theme - static ThemeData lightTheme(BuildContext context) { + static ThemeData lightTheme(BuildContext context, {CorePalette? palette}) { var lightColors = LightAppColors(); - Color accent = accentColorMap[Provider.of(context, listen: false).accentColor] ?? const Color(0x00000000); + AccentColor accentColor = Provider.of(context, listen: false).accentColor; + Color accent = accentColorMap[accentColor] ?? const Color(0x00000000); + + if (accentColor == AccentColor.adaptive) { + if (palette != null) accent = _paletteAccentLight(palette)!; + } else { + palette = null; + } + return ThemeData( + brightness: Brightness.light, + fontFamily: _fontFamily, + scaffoldBackgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background, + backgroundColor: _paletteHighlightLight(palette) ?? lightColors.highlight, + primaryColor: lightColors.filc, + dividerColor: const Color(0x00000000), + colorScheme: ColorScheme.fromSwatch( + accentColor: accent, + backgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background, brightness: Brightness.light, - fontFamily: _fontFamily, - scaffoldBackgroundColor: lightColors.background, - backgroundColor: lightColors.highlight, - primaryColor: lightColors.filc, - dividerColor: const Color(0x00000000), - colorScheme: ColorScheme.fromSwatch( - accentColor: accent, - backgroundColor: lightColors.background, - brightness: Brightness.light, - cardColor: lightColors.highlight, - errorColor: lightColors.red, - primaryColorDark: lightColors.filc, - primarySwatch: Colors.teal, - ), - shadowColor: lightColors.shadow, - appBarTheme: AppBarTheme(backgroundColor: lightColors.background), - indicatorColor: accent, - iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75))); + cardColor: _paletteHighlightLight(palette) ?? lightColors.highlight, + errorColor: lightColors.red, + primaryColorDark: lightColors.filc, + primarySwatch: Colors.teal, + ), + shadowColor: lightColors.shadow, + appBarTheme: AppBarTheme(backgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background), + indicatorColor: accent, + iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75)), + navigationBarTheme: NavigationBarThemeData( + indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8), + iconTheme: MaterialStateProperty.all(IconThemeData(color: lightColors.text)), + backgroundColor: _paletteHighlightLight(palette) ?? lightColors.highlight, + labelTextStyle: MaterialStateProperty.all(TextStyle( + fontSize: 13.0, + fontWeight: FontWeight.w500, + color: lightColors.text.withOpacity(0.8), + )), + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, + height: 76.0, + ), + sliderTheme: SliderThemeData( + inactiveTrackColor: accent.withOpacity(.3), + ), + progressIndicatorTheme: ProgressIndicatorThemeData(color: accent), + expansionTileTheme: ExpansionTileThemeData(iconColor: accent), + ); } // Dark Theme - static ThemeData darkTheme(BuildContext context) { + static ThemeData darkTheme(BuildContext context, {CorePalette? palette}) { var darkColors = DarkAppColors(); - Color accent = accentColorMap[Provider.of(context, listen: false).accentColor] ?? const Color(0x00000000); + AccentColor accentColor = Provider.of(context, listen: false).accentColor; + Color accent = accentColorMap[accentColor] ?? const Color(0x00000000); + + if (accentColor == AccentColor.adaptive) { + if (palette != null) accent = _paletteAccentDark(palette)!; + } else { + palette = null; + } + return ThemeData( + brightness: Brightness.dark, + fontFamily: _fontFamily, + scaffoldBackgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background, + backgroundColor: _paletteHighlightDark(palette) ?? darkColors.highlight, + primaryColor: darkColors.filc, + dividerColor: const Color(0x00000000), + colorScheme: ColorScheme.fromSwatch( + accentColor: accent, + backgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background, brightness: Brightness.dark, - fontFamily: _fontFamily, - scaffoldBackgroundColor: darkColors.background, - backgroundColor: darkColors.highlight, - primaryColor: darkColors.filc, - dividerColor: const Color(0x00000000), - colorScheme: ColorScheme.fromSwatch( - accentColor: accent, - backgroundColor: darkColors.background, - brightness: Brightness.dark, - cardColor: darkColors.highlight, - errorColor: darkColors.red, - primaryColorDark: darkColors.filc, - primarySwatch: Colors.teal, - ), - shadowColor: darkColors.shadow, - appBarTheme: AppBarTheme(backgroundColor: darkColors.background), - indicatorColor: accent, - iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75))); + cardColor: _paletteHighlightDark(palette) ?? darkColors.highlight, + errorColor: darkColors.red, + primaryColorDark: darkColors.filc, + primarySwatch: Colors.teal, + ), + shadowColor: darkColors.shadow, + appBarTheme: AppBarTheme(backgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background), + indicatorColor: accent, + iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75)), + navigationBarTheme: NavigationBarThemeData( + indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8), + iconTheme: MaterialStateProperty.all(IconThemeData(color: darkColors.text)), + backgroundColor: _paletteHighlightDark(palette) ?? darkColors.highlight, + labelTextStyle: MaterialStateProperty.all(TextStyle( + fontSize: 13.0, + fontWeight: FontWeight.w500, + color: darkColors.text.withOpacity(0.8), + )), + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, + height: 76.0, + ), + sliderTheme: SliderThemeData( + inactiveTrackColor: accent.withOpacity(.3), + ), + progressIndicatorTheme: ProgressIndicatorThemeData(color: accent), + expansionTileTheme: ExpansionTileThemeData(iconColor: accent), + ); } } @@ -67,18 +128,19 @@ class AppColors { } } -enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple } +enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple, adaptive } Map accentColorMap = { AccentColor.filc: const Color(0xff20AC9B), AccentColor.blue: Colors.blue.shade300, - AccentColor.green: Colors.green.shade300, - AccentColor.lime: Colors.lime.shade300, - AccentColor.yellow: Colors.yellow.shade300, + AccentColor.green: Colors.green.shade400, + AccentColor.lime: Colors.lightGreen.shade400, + AccentColor.yellow: Colors.orange.shade300, AccentColor.orange: Colors.deepOrange.shade300, AccentColor.red: Colors.red.shade300, AccentColor.pink: Colors.pink.shade300, AccentColor.purple: Colors.purple.shade300, + AccentColor.adaptive: const Color(0xff20AC9B), }; abstract class ThemeAppColors { diff --git a/filcnaplo/lib/utils/reverse_search.dart b/filcnaplo/lib/utils/reverse_search.dart new file mode 100644 index 0000000..2bdb67e --- /dev/null +++ b/filcnaplo/lib/utils/reverse_search.dart @@ -0,0 +1,37 @@ +import 'dart:developer'; + +import 'package:filcnaplo_kreta_api/models/absence.dart'; +import 'package:filcnaplo_kreta_api/models/lesson.dart'; +import 'package:filcnaplo_kreta_api/models/week.dart'; +import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +class ReverseSearch { + static Future getLessonByAbsence(Absence absence, BuildContext context) async { + final timetableProvider = Provider.of(context, listen: false); + + List lessons = []; + try { + await timetableProvider.fetch(week: Week.fromDate(absence.date), db: false); + } catch (e) { + log("[ERROR] getLessonByAbsence: $e"); + } + lessons = timetableProvider.lessons; + + // Find absence lesson in timetable + Lesson lesson = lessons.firstWhere( + (l) => _sameDate(l.date, absence.date) && l.subject.id == absence.subject.id && l.lessonIndex == absence.lessonIndex.toString(), + orElse: () => Lesson.fromJson({'isEmpty': true}), + ); + + if (lesson.isEmpty) { + return null; + } else { + return lesson; + } + } + + // difference.inDays is not reliable + static bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day); +} diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml index 780a80a..506948d 100644 --- a/filcnaplo/pubspec.yaml +++ b/filcnaplo/pubspec.yaml @@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez" homepage: https://filcnaplo.hu publish_to: "none" -version: 3.2.3+148 +version: 3.3.0-beta.6+162 environment: sdk: ">=2.16.0-80.1.beta <3.0.0" @@ -32,17 +32,19 @@ dependencies: html: ^0.15.0 open_file: ^3.2.1 path_provider: ^2.0.2 - permission_handler: ^8.3.0 - share_plus: ^3.0.4 + permission_handler: ^9.2.0 + share_plus: ^4.0.4 connectivity_plus: ^2.0.2 - flutter_displaymode: ^0.3.2 + flutter_displaymode: ^0.4.0 quick_actions: ^0.6.0 implicitly_animated_reorderable_list: ^0.4.2 + dynamic_color: ^1.2.2 + material_color_utilities: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^1.0.0 + flutter_lints: ^2.0.1 # flutter_launcher_icons: ^0.9.0 # flutter_native_splash: ^1.2.0 sqflite_common_ffi: ^2.0.0+3 diff --git a/filcnaplo_kreta_api b/filcnaplo_kreta_api index 1325811..85e9565 160000 --- a/filcnaplo_kreta_api +++ b/filcnaplo_kreta_api @@ -1 +1 @@ -Subproject commit 132581180c76fd10960abe49f073629e772cfc66 +Subproject commit 85e956539e717d8806ec74389e47bad3c9371160 diff --git a/filcnaplo_mobile_ui b/filcnaplo_mobile_ui index bee4c6c..c7dc6d7 160000 --- a/filcnaplo_mobile_ui +++ b/filcnaplo_mobile_ui @@ -1 +1 @@ -Subproject commit bee4c6cdd9561cfff62303fcc7c7a1abe81c3cf5 +Subproject commit c7dc6d7d1f5e52387ab4ecf61581893fe929dc2e