From b9e9bef18297baa93e80c3bba58f68d33d81a264 Mon Sep 17 00:00:00 2001 From: balint1414 Date: Tue, 3 Sep 2024 18:49:55 +0200 Subject: [PATCH 1/8] =?UTF-8?q?Fektetett=20=C3=B3rarend=20hib=C3=A1s=20ki?= =?UTF-8?q?=C3=ADr=C3=A1s=20jav=C3=ADt=C3=A1sa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/pages/timetable/fs_timetable.dart | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart b/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart index 64e3be0..59ed18c 100644 --- a/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart +++ b/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart @@ -78,7 +78,7 @@ class _FSTimetableState extends State { itemCount: maxLessonCount + 1, itemBuilder: (context, index) { List columns = []; - for (int dayIndex = -1; dayIndex < days.length; dayIndex++) { + for (int dayIndex = -1; dayIndex < days.length; dayIndex++) { if (dayIndex == -1) { if (index >= 1) { columns.add(SizedBox( @@ -87,7 +87,7 @@ class _FSTimetableState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( - "${index - 1}.", + "${index - 0}.", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, @@ -119,9 +119,6 @@ class _FSTimetableState extends State { if (lessons.isEmpty) continue; - int lsnIndx = int.tryParse(lessons.first.lessonIndex) ?? 1; - final dayOffset = lsnIndx == 0 ? 1 : lsnIndx; - if (index == 0 && dayIndex >= 0) { columns.add( SizedBox( @@ -141,16 +138,10 @@ class _FSTimetableState extends State { continue; } - final lessonIndex = index - dayOffset; - Lesson? lsn = lessons.firstWhereOrNull( - (e) => e.lessonIndex == (index - 1).toString()); + (e) => e.lessonIndex == index.toString()); - if (lessonIndex < 0 || - lessonIndex > lessons.length || - (index == 1 && lsnIndx != 0) || - (lsnIndx != 0 && lessonIndex - 1 == -1) || - lsn == null) { + if (lsn == null) { columns.add(SizedBox(width: colw)); continue; } From cf0dc50df5d52a27f8ac802be345a40c3c31c712 Mon Sep 17 00:00:00 2001 From: balint1414 Date: Fri, 15 Nov 2024 20:43:18 +0100 Subject: [PATCH 2/8] =?UTF-8?q?Fektetett=20=C3=B3rarend:=200.=20=C3=B3ra?= =?UTF-8?q?=20=C3=A9s=20utols=C3=B3=20=C3=B3ra=20megjelen=C3=ADt=C3=A9se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/pages/timetable/fs_timetable.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart b/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart index 59ed18c..ad2f61a 100644 --- a/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart +++ b/refilc_mobile_ui/lib/pages/timetable/fs_timetable.dart @@ -75,10 +75,10 @@ class _FSTimetableState extends State { body: ListView.builder( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 24.0), - itemCount: maxLessonCount + 1, + itemCount: maxLessonCount + 2, itemBuilder: (context, index) { List columns = []; - for (int dayIndex = -1; dayIndex < days.length; dayIndex++) { + for (int dayIndex = -1; dayIndex < days.length; dayIndex++) { if (dayIndex == -1) { if (index >= 1) { columns.add(SizedBox( @@ -87,7 +87,7 @@ class _FSTimetableState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( - "${index - 0}.", + "${index - 1}.", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, @@ -120,6 +120,7 @@ class _FSTimetableState extends State { if (lessons.isEmpty) continue; if (index == 0 && dayIndex >= 0) { + // if (index == 0 || dayIndex >=0) { columns.add( SizedBox( width: colw, @@ -139,7 +140,7 @@ class _FSTimetableState extends State { } Lesson? lsn = lessons.firstWhereOrNull( - (e) => e.lessonIndex == index.toString()); + (e) => e.lessonIndex == (index - 1).toString()); if (lsn == null) { columns.add(SizedBox(width: colw)); @@ -250,4 +251,4 @@ class _FSTimetableState extends State { ), ); } -} +} \ No newline at end of file From 41b1d899d08b1e0204ab36de0b80b07b6acce23e Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 15 Nov 2024 23:52:40 +0100 Subject: [PATCH 3/8] removed unused dependencies --- refilc/pubspec.yaml | 14 +++++++------- refilc_mobile_ui/pubspec.yaml | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index aad21c5..88aa22c 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -49,7 +49,7 @@ dependencies: crypto: ^3.0.2 elegant_notification: ^2.2.0 flutter_feather_icons: ^2.0.0+1 - live_activities: ^1.7.4 + # live_activities: ^1.7.4 animated_flip_counter: ^0.3.4 lottie: ^3.1.0 rive: ^0.12.4 @@ -62,10 +62,10 @@ dependencies: flutter_expandable_fab: ^2.0.0 uni_links: ^0.5.1 url_launcher: ^6.1.6 - workmanager: - git: - url: https://github.com/refilc/flutter_workmanager.git - ref: v0.5.1 + # workmanager: + # git: + # url: https://github.com/refilc/flutter_workmanager.git + # ref: v0.5.1 flutter_svg: ^2.0.10+1 image_picker: ^1.0.7 animations: ^2.0.1 @@ -83,11 +83,11 @@ dependencies: extension_google_sign_in_as_googleapis_auth: ^2.0.12 maps_launcher: ^2.2.0 google_fonts: ^6.1.0 - flutter_stripe: ^10.0.0 + # flutter_stripe: ^10.0.0 get_it: ^7.6.7 xml: ^6.5.0 carousel_slider: ^4.2.1 - flutter_portal: ^1.1.4 + # flutter_portal: ^1.1.4 shake_flutter: ^17.0.0 dev_dependencies: diff --git a/refilc_mobile_ui/pubspec.yaml b/refilc_mobile_ui/pubspec.yaml index 7c050a9..d1250dc 100644 --- a/refilc_mobile_ui/pubspec.yaml +++ b/refilc_mobile_ui/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: animations: ^2.0.11 animated_list_plus: ^0.5.0 confetti: ^0.7.0 - live_activities: ^1.9.1+1 + # live_activities: ^1.9.1+1 animated_flip_counter: ^0.3.4 lottie: ^3.1.0 rive: ^0.12.4 @@ -51,7 +51,7 @@ dependencies: rounded_expansion_tile: git: url: https://github.com/kimaah/rounded_expansion_tile.git - go_router: ^14.2.0 + # go_router: ^14.2.0 flutter_expandable_fab: ^2.0.0 intl: ^0.19.0 i18n_extension: ^12.0.1 @@ -67,13 +67,13 @@ dependencies: uuid: ^4.3.3 maps_launcher: ^2.2.0 google_fonts: ^6.1.0 - flutter_any_logo: ^1.1.1 + # flutter_any_logo: ^1.1.1 custom_sliding_segmented_control: ^1.8.1 get_it: ^7.6.7 xml: ^6.5.0 markdown: ^7.2.2 carousel_slider: ^4.2.1 - flutter_portal: ^1.1.4 + # flutter_portal: ^1.1.4 webview_flutter: ^4.8.0 file_picker: ^8.0.5 shake_flutter: ^17.0.0 From a218b62742752d435e3d404de03f96697ab1421d Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 15 Nov 2024 23:53:39 +0100 Subject: [PATCH 4/8] removed unused dependencies --- refilc_plus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refilc_plus b/refilc_plus index 6abc4ed..bc6ccd9 160000 --- a/refilc_plus +++ b/refilc_plus @@ -1 +1 @@ -Subproject commit 6abc4edf70deeaffea8b8a7dd95acebecc5a520b +Subproject commit bc6ccd961b8e3edf35d279d32c78113fbd1d7a5a From 8723f7588978ed27dd942ced28d424c1e34f90b3 Mon Sep 17 00:00:00 2001 From: balint1414 Date: Sat, 16 Nov 2024 12:25:27 +0100 Subject: [PATCH 5/8] =?UTF-8?q?gradestreak=20probl=C3=A9ma=20jav=C3=ADt?= =?UTF-8?q?=C3=A1s:=20nem=20ignor=C3=A1lja=20a=20sz=C3=B6veges=20=C3=A9rt?= =?UTF-8?q?=C3=A9kel=C3=A9st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refilc_kreta_api/lib/providers/grade_provider.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/refilc_kreta_api/lib/providers/grade_provider.dart b/refilc_kreta_api/lib/providers/grade_provider.dart index 210be49..b17b8ed 100644 --- a/refilc_kreta_api/lib/providers/grade_provider.dart +++ b/refilc_kreta_api/lib/providers/grade_provider.dart @@ -153,6 +153,8 @@ class GradeProvider with ChangeNotifier { for (Grade grade in grs) { if (grade.value.value == 5) { gradeStreak++; + } else if (grade.value.value == 0){ + } else { break; } From 071f682f77b55fa590dc05aeb5f2307825f5fa46 Mon Sep 17 00:00:00 2001 From: balint1414 Date: Sat, 16 Nov 2024 15:41:41 +0100 Subject: [PATCH 6/8] =?UTF-8?q?gradestreak:=20jav=C3=ADt=C3=A1s=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refilc_kreta_api/lib/providers/grade_provider.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/refilc_kreta_api/lib/providers/grade_provider.dart b/refilc_kreta_api/lib/providers/grade_provider.dart index b17b8ed..804b1e9 100644 --- a/refilc_kreta_api/lib/providers/grade_provider.dart +++ b/refilc_kreta_api/lib/providers/grade_provider.dart @@ -153,9 +153,7 @@ class GradeProvider with ChangeNotifier { for (Grade grade in grs) { if (grade.value.value == 5) { gradeStreak++; - } else if (grade.value.value == 0){ - - } else { + } else if (grade.value.value !=0) { break; } } From 7d5b97fe004f83e3bc46bdf842400433c718320b Mon Sep 17 00:00:00 2001 From: Kima Date: Sat, 16 Nov 2024 22:21:22 +0100 Subject: [PATCH 7/8] started working on cloud sync (testing) --- refilc/lib/api/client.dart | 29 ++ refilc/lib/database/init.dart | 2 + refilc/lib/models/cloud_sync_data.dart | 22 ++ refilc/lib/models/settings.dart | 113 ++++++++ refilc/lib/models/user.dart | 17 ++ .../lib/screens/login/qwid_login.dart | 170 ++++++++++++ .../lib/screens/settings/settings_screen.dart | 8 + .../settings/submenu/cloud_sync_screen.dart | 250 ++++++++++++++++++ .../settings/submenu/submenu_screen.i18n.dart | 6 + 9 files changed, 617 insertions(+) create mode 100644 refilc/lib/models/cloud_sync_data.dart create mode 100644 refilc_mobile_ui/lib/screens/login/qwid_login.dart create mode 100644 refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index ed02aa1..93bfd7a 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -54,6 +54,9 @@ class FilcAPI { static const payment = "$baseUrl/v4/payment"; static const stripeSheet = "$payment/stripe-sheet"; + // Cloud Sync + static const cloudSyncApi = "$baseUrl/v4/me/cloud-sync"; + static Future checkConnectivity() async => (await Connectivity().checkConnectivity())[0] != ConnectivityResult.none; @@ -390,6 +393,32 @@ class FilcAPI { return null; } + + // cloud sync + static Future cloudSync(Map data, String token) async { + try { + var client = http.Client(); + + http.Response res = await client.post( + Uri.parse(cloudSyncApi), + body: data, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Bearer $token', + }, + ); + + if (res.statusCode != 200) { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + + return jsonDecode(res.body); + } on Exception catch (error, stacktrace) { + log("ERROR: FilcAPI.cloudSync: $error $stacktrace"); + } + + return null; + } } class ErrorReport { diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index 2850814..a693315 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -56,6 +56,8 @@ const settingsDB = DatabaseStruct("settings", { "uwu_mode": int, "new_popups": int, "unseen_new_features": String, + "cloud_sync_enabled": int, + "cloud_sync_token": String, // quick settings "q_timetable_lesson_num": int, "q_timetable_sub_tiles": int, "q_subjects_sub_tiles": int, diff --git a/refilc/lib/models/cloud_sync_data.dart b/refilc/lib/models/cloud_sync_data.dart new file mode 100644 index 0000000..71ab4d9 --- /dev/null +++ b/refilc/lib/models/cloud_sync_data.dart @@ -0,0 +1,22 @@ +class CloudSyncData { + Map settings; + List deviceIds; + String reFilcPlusId; + Map json; + + CloudSyncData({ + this.settings = const {}, + this.deviceIds = const [], + this.reFilcPlusId = "", + required this.json, + }); + + factory CloudSyncData.fromJson(Map json) { + return CloudSyncData( + settings: json['settings'] ?? {}, + deviceIds: List.from(json['device_ids'] ?? []), + reFilcPlusId: json['refilc_plus_id'] ?? "", + json: json, + ); + } +} diff --git a/refilc/lib/models/settings.dart b/refilc/lib/models/settings.dart index 1863941..f05ff87 100644 --- a/refilc/lib/models/settings.dart +++ b/refilc/lib/models/settings.dart @@ -109,6 +109,8 @@ class SettingsProvider extends ChangeNotifier { bool _uwuMode; bool _newPopups; List _unseenNewFeatures; + bool _cloudSyncEnabled; + String _cloudSyncToken; // quick settings bool _qTimetableLessonNum; bool _qTimetableSubTiles; @@ -184,6 +186,8 @@ class SettingsProvider extends ChangeNotifier { required bool uwuMode, required bool newPopups, required List unseenNewFeatures, + required bool cloudSyncEnabled, + required String cloudSyncToken, required bool qTimetableLessonNum, required bool qTimetableSubTiles, required bool qSubjectsSubTiles, @@ -256,6 +260,8 @@ class SettingsProvider extends ChangeNotifier { _uwuMode = uwuMode, _newPopups = newPopups, _unseenNewFeatures = unseenNewFeatures, + _cloudSyncEnabled = cloudSyncEnabled, + _cloudSyncToken = cloudSyncToken, _qTimetableLessonNum = qTimetableLessonNum, _qTimetableSubTiles = qTimetableSubTiles, _qSubjectsSubTiles = qSubjectsSubTiles; @@ -347,6 +353,8 @@ class SettingsProvider extends ChangeNotifier { uwuMode: map['uwu_mode'] == 1, newPopups: map['new_popups'] == 1, unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast(), + cloudSyncEnabled: map['cloud_sync_enabled'] == 1, + cloudSyncToken: map['cloud_sync_token'], qTimetableLessonNum: map['q_timetable_lesson_num'] == 1, qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1, qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1, @@ -426,6 +434,8 @@ class SettingsProvider extends ChangeNotifier { "uwu_mode": _uwuMode ? 1 : 0, "new_popups": _newPopups ? 1 : 0, "unseen_new_features": jsonEncode(_unseenNewFeatures), + "cloud_sync_enabled": _cloudSyncEnabled ? 1 : 0, + "cloud_sync_token": _cloudSyncToken, "q_timetable_lesson_num": _qTimetableLessonNum ? 1 : 0, "q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0, "q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0, @@ -509,6 +519,8 @@ class SettingsProvider extends ChangeNotifier { uwuMode: false, newPopups: true, unseenNewFeatures: ['grade_exporting'], + cloudSyncEnabled: false, + cloudSyncToken: '', qTimetableLessonNum: true, qTimetableSubTiles: true, qSubjectsSubTiles: true, @@ -583,6 +595,8 @@ class SettingsProvider extends ChangeNotifier { bool get uwuMode => _uwuMode; bool get newPopups => _newPopups; List get unseenNewFeatures => _unseenNewFeatures; + bool get cloudSyncEnabled => _cloudSyncEnabled; + String get cloudSyncToken => _cloudSyncToken; bool get qTimetableLessonNum => _qTimetableLessonNum; bool get qTimetableSubTiles => _qTimetableSubTiles; bool get qSubjectsSubTiles => _qSubjectsSubTiles; @@ -597,6 +611,7 @@ class SettingsProvider extends ChangeNotifier { List? gradeColors, bool? newsEnabled, String? seenNewsId, + String? seenNews, // only for restoring from map bool? notificationsEnabled, bool? notificationsGradesEnabled, bool? notificationsAbsencesEnabled, @@ -653,6 +668,8 @@ class SettingsProvider extends ChangeNotifier { bool? uwuMode, bool? newPopups, List? unseenNewFeatures, + bool? cloudSyncEnabled, + String? cloudSyncToken, bool? qTimetableLessonNum, bool? qTimetableSubTiles, bool? qSubjectsSubTiles, @@ -675,6 +692,7 @@ class SettingsProvider extends ChangeNotifier { tempList.add(seenNewsId); _seenNews = tempList.join(','); } + if (seenNews != null && seenNews != _seenNews) _seenNews = seenNews; if (notificationsEnabled != null && notificationsEnabled != _notificationsEnabled) { _notificationsEnabled = notificationsEnabled; @@ -850,6 +868,12 @@ class SettingsProvider extends ChangeNotifier { if (unseenNewFeatures != null && unseenNewFeatures != _unseenNewFeatures) { _unseenNewFeatures = unseenNewFeatures; } + if (cloudSyncEnabled != null && cloudSyncEnabled != _cloudSyncEnabled) { + _cloudSyncEnabled = cloudSyncEnabled; + } + if (cloudSyncToken != null && cloudSyncToken != _cloudSyncToken) { + _cloudSyncToken = cloudSyncToken; + } if (qTimetableLessonNum != null && qTimetableLessonNum != _qTimetableLessonNum) { _qTimetableLessonNum = qTimetableLessonNum; @@ -866,6 +890,95 @@ class SettingsProvider extends ChangeNotifier { notifyListeners(); } + Future updateFromMap({ + required Map map, + bool store = true, + }) async { + await update( + store: store, + language: map["language"], + startPage: Pages.values[map["start_page"]], + rounding: map["rounding"], + theme: ThemeMode.values[map["theme"]], + accentColor: AccentColor.values[map["accent_color"]], + gradeColors: [ + Color(map["grade_color1"]), + Color(map["grade_color2"]), + Color(map["grade_color3"]), + Color(map["grade_color4"]), + Color(map["grade_color5"]), + ], + newsEnabled: map["news"] == 1, + seenNews: map["seen_news"], + notificationsEnabled: map["notifications"] == 1, + notificationsGradesEnabled: map["notifications_grades"] == 1, + notificationsAbsencesEnabled: map["notifications_absences"] == 1, + notificationsMessagesEnabled: map["notifications_messages"] == 1, + notificationsLessonsEnabled: map["notifications_lessons"] == 1, + notificationsBitfield: map["notifications_bitfield"], + notificationPollInterval: map["notification_poll_interval"], + developerMode: map["developer_mode"] == 1, + vibrate: VibrationStrength.values[map["vibration_strength"]], + abWeeks: map["ab_weeks"] == 1, + swapABweeks: map["swap_ab_weeks"] == 1, + updateChannel: UpdateChannel.values[map["update_channel"]], + config: Config.fromJson(jsonDecode(map["config"])), + xFilcId: map["x_filc_id"], + analyticsEnabled: map["analytics_enabled"] == 1, + graphClassAvg: map["graph_class_avg"] == 1, + goodStudent: false, + presentationMode: map["presentation_mode"] == 1, + bellDelayEnabled: map["bell_delay_enabled"] == 1, + bellDelay: map["bell_delay"], + gradeOpeningFun: map["grade_opening_fun"] == 1, + iconPack: Map.fromEntries( + IconPack.values.map((e) => MapEntry(e.name, e)))[map["icon_pack"]]!, + customAccentColor: Color(map["custom_accent_color"]), + customBackgroundColor: Color(map["custom_background_color"]), + customHighlightColor: Color(map["custom_highlight_color"]), + customIconColor: Color(map["custom_icon_color"]), + customTextColor: Color(map["custom_text_color"]), + shadowEffect: map["shadow_effect"] == 1, + premiumScopes: jsonDecode(map["premium_scopes"]).cast(), + premiumAccessToken: map["premium_token"], + premiumLogin: map["premium_login"], + lastAccountId: map["last_account_id"], + renamedSubjectsEnabled: map["renamed_subjects_enabled"] == 1, + renamedSubjectsItalics: map["renamed_subjects_italics"] == 1, + renamedTeachersEnabled: map["renamed_teachers_enabled"] == 1, + renamedTeachersItalics: map["renamed_teachers_italics"] == 1, + liveActivityColor: Color(map["live_activity_color"]), + welcomeMessage: map["welcome_message"], + appIcon: map["app_icon"], + currentThemeId: map['current_theme_id'], + currentThemeDisplayName: map['current_theme_display_name'], + currentThemeCreator: map['current_theme_creator'], + showBreaks: map['show_breaks'] == 1, + // pinSetGeneral: map['general_s_pin'], + // pinSetPersonalize: map['personalize_s_pin'], + // pinSetNotify: map['notify_s_pin'], + // pinSetExtras: map['extras_s_pin'], + fontFamily: map['font_family'], + titleOnlyFont: map['title_only_font'] == 1, + plusSessionId: map['plus_session_id'], + calSyncRoomLocation: map['cal_sync_room_location'], + calSyncShowExams: map['cal_sync_show_exams'] == 1, + calSyncShowTeacher: map['cal_sync_show_teacher'] == 1, + calSyncRenamed: map['cal_sync_renamed'] == 1, + calendarId: map['calendar_id'], + navShadow: map['nav_shadow'] == 1, + newColors: map['new_colors'] == 1, + uwuMode: map['uwu_mode'] == 1, + newPopups: map['new_popups'] == 1, + unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast(), + cloudSyncEnabled: map['cloud_sync_enabled'] == 1, + cloudSyncToken: map['cloud_sync_token'], + qTimetableLessonNum: map['q_timetable_lesson_num'] == 1, + qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1, + qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1, + ); + } + void exportJson() { String sets = json.encode(toMap()); Clipboard.setData(ClipboardData(text: sets)); diff --git a/refilc/lib/models/user.dart b/refilc/lib/models/user.dart index e4fca5d..33b9e73 100644 --- a/refilc/lib/models/user.dart +++ b/refilc/lib/models/user.dart @@ -21,6 +21,10 @@ class User { String accessToken; DateTime accessTokenExpire; String refreshToken; + // cloud sync + // String qwidAccessToken; + // DateTime? qwidAccessTokenExpire; + // String qwidRefreshToken; String get displayName => nickname != '' ? nickname : name; bool get hasStreak => gradeStreak > 0; @@ -39,6 +43,9 @@ class User { required this.accessToken, required this.accessTokenExpire, required this.refreshToken, + // this.qwidAccessToken = "", + // this.qwidAccessTokenExpire, + // this.qwidRefreshToken = "", }) { if (id != null) { this.id = id; @@ -74,6 +81,11 @@ class User { ? map["access_token_expire"] : DateTime.now().toIso8601String()), refreshToken: map["refresh_token"] ?? "", + // qwidAccessToken: map["qwid_access_token"] ?? "", + // qwidAccessTokenExpire: map["qwid_access_token_expire"] != "" + // ? DateTime.parse(map["qwid_access_token_expire"]) + // : null, + // qwidRefreshToken: map["qwid_refresh_token"] ?? "", ); } @@ -92,6 +104,11 @@ class User { "access_token": accessToken, "access_token_expire": accessTokenExpire.toIso8601String(), "refresh_token": refreshToken, + // "qwid_access_token": qwidAccessToken, + // "qwid_access_token_expire": qwidAccessTokenExpire != null + // ? qwidAccessTokenExpire!.toIso8601String() + // : "", + // "qwid_refresh_token": qwidRefreshToken, }; } diff --git a/refilc_mobile_ui/lib/screens/login/qwid_login.dart b/refilc_mobile_ui/lib/screens/login/qwid_login.dart new file mode 100644 index 0000000..cc1428e --- /dev/null +++ b/refilc_mobile_ui/lib/screens/login/qwid_login.dart @@ -0,0 +1,170 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class QwIDLoginWidget extends StatefulWidget { + const QwIDLoginWidget({super.key, required this.onLogin}); + + // final String selectedSchool; + final void Function(String code) onLogin; + + @override + State createState() => _QwIDLoginWidgetState(); +} + +class _QwIDLoginWidgetState extends State + with TickerProviderStateMixin { + late final WebViewController controller; + late AnimationController _animationController; + var loadingPercentage = 0; + var currentUrl = ''; + bool _hasFadedIn = false; + + @override + void initState() { + super.initState(); + + _animationController = AnimationController( + vsync: this, // Use the TickerProviderStateMixin + duration: const Duration(milliseconds: 350), + ); + + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onNavigationRequest: (n) async { + if (n.url.startsWith('refilc://oauth2-callback/qwid')) { + setState(() { + loadingPercentage = 0; + currentUrl = n.url; + }); + + // final String instituteCode = widget.selectedSchool; + // if (!n.url.startsWith( + // 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) { + // return; + // } + + String longLivedToken = n.url + .replaceAll('refilc://oauth2-callback/qwid?access_token=', ''); + + widget.onLogin(longLivedToken); + // Future.delayed(const Duration(milliseconds: 500), () { + // Navigator.of(context).pop(); + // }); + // Navigator.of(context).pop(); + + return NavigationDecision.prevent; + } else { + return NavigationDecision.navigate; + } + }, + onPageStarted: (url) async { + // setState(() { + // loadingPercentage = 0; + // currentUrl = url; + // }); + + // // final String instituteCode = widget.selectedSchool; + // if (!url.startsWith( + // 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) { + // return; + // } + + // List requiredThings = url + // .replaceAll( + // 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=', + // '') + // .replaceAll( + // '&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=', + // ':') + // .split(':'); + + // String code = requiredThings[0]; + // // String sessionState = requiredThings[1]; + + // widget.onLogin(code); + // // Future.delayed(const Duration(milliseconds: 500), () { + // // Navigator.of(context).pop(); + // // }); + // // Navigator.of(context).pop(); + }, + onProgress: (progress) { + setState(() { + loadingPercentage = progress; + }); + }, + onPageFinished: (url) { + setState(() { + loadingPercentage = 100; + }); + }, + )) + ..loadRequest( + Uri.parse( + 'https://qwid.qwit.dev/oauth2/authorize?client_id=c3b871fb-d922-4e23-b94d-b31f294c9253&scope=*&redirect_uri=https://api.refilc.hu/v4/oauth2/callback/app/qwid&response_type=code'), // &institute_code=${widget.selectedSchool} + ); + } + + // Future loadLoginUrl() async { + // String nonceStr = await Provider.of(context, listen: false) + // .getAPI(KretaAPI.nonce, json: false); + + // Nonce nonce = getNonce(nonceStr, ); + // } + + @override + void dispose() { + // Step 3: Dispose of the animation controller + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Trigger the fade-in animation only once when loading reaches 100% + if (loadingPercentage == 100 && !_hasFadedIn) { + _animationController.forward(); // Play the animation + _hasFadedIn = + true; // Set the flag to true, so the animation is not replayed + } + + return Stack( + children: [ + // Webview that will be displayed only when the loading is 100% + if (loadingPercentage == 100) + FadeTransition( + opacity: Tween(begin: 0, end: 1).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeIn, + ), + ), + child: WebViewWidget( + controller: controller, + ), + ), + + // Show the CircularProgressIndicator while loading is not 100% + if (loadingPercentage < 100) + Center( + child: TweenAnimationBuilder( + tween: Tween(begin: 0, end: loadingPercentage / 100.0), + duration: const Duration(milliseconds: 300), + builder: (context, double value, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + value: value, // Smoothly animates the progress + ), + ], + ); + }, + ), + ), + ], + ); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index 6868474..c312771 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -68,6 +68,7 @@ import 'package:refilc_mobile_ui/screens/settings/user/profile_pic.dart'; // import 'package:refilc_plus/ui/mobile/settings/welcome_message.dart'; // import 'package:refilc_mobile_ui/screens/error_screen.dart'; import 'package:refilc_mobile_ui/screens/error_report_screen.dart'; +import 'submenu/cloud_sync_screen.dart'; import 'submenu/general_screen.dart'; import 'package:refilc_plus/ui/mobile/plus/settings_inline.dart'; @@ -428,6 +429,13 @@ class SettingsScreenState extends State borderRadius: const BorderRadius.vertical( top: Radius.circular(12.0), bottom: Radius.circular(4.0)), ), + // cloud-sync + const MenuCloudSyncSettings( + borderRadius: BorderRadius.vertical( + top: Radius.circular(4.0), + bottom: Radius.circular(4.0), + ), + ), // open dcs (digital collaboration space) PanelButton( onPressed: () => _openDKT(user.user!), diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart b/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart new file mode 100644 index 0000000..222ade5 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart @@ -0,0 +1,250 @@ +// import 'package:refilc/models/settings.dart'; +import 'package:refilc/api/client.dart'; +import 'package:refilc/api/providers/database_provider.dart'; +import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/models/settings.dart'; +import 'package:refilc/theme/colors/colors.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/screens/login/qwid_login.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc/models/cloud_sync_data.dart'; +// import 'package:provider/provider.dart'; +import 'submenu_screen.i18n.dart'; + +class MenuCloudSyncSettings extends StatelessWidget { + const MenuCloudSyncSettings({ + super.key, + this.borderRadius = const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), + }); + + final BorderRadius borderRadius; + + @override + Widget build(BuildContext context) { + return PanelButton( + onPressed: () => Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => const CloudSyncSettingsScreen()), + ), + title: Text("cloud_sync".i18n), + leading: Icon( + FeatherIcons.uploadCloud, + size: 22.0, + color: AppColors.of(context).text.withOpacity(0.95), + ), + borderRadius: borderRadius, + ); + } +} + +class CloudSyncSettingsScreen extends StatefulWidget { + const CloudSyncSettingsScreen({super.key}); + + @override + CloudSyncSettingsScreenState createState() => CloudSyncSettingsScreenState(); +} + +class CloudSyncSettingsScreenState extends State { + late SettingsProvider settingsProvider; + late UserProvider user; + + String longLivedToken = ''; + + @override + Widget build(BuildContext context) { + SettingsProvider settingsProvider = Provider.of(context); + // UserProvider user = Provider.of(context); + + return Scaffold( + appBar: AppBar( + surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, + leading: BackButton(color: AppColors.of(context).text), + title: Text( + "cloud_sync".i18n, + style: TextStyle(color: AppColors.of(context).text), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), + child: Column( + children: [ + SplittedPanel( + padding: const EdgeInsets.only(top: 8.0), + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + PanelButton( + padding: const EdgeInsets.only(left: 14.0, right: 6.0), + onPressed: () async { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + isScrollControlled: + true, // This ensures the modal accommodates input fields properly + builder: (BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height * 0.9 + + MediaQuery.of(context).viewInsets.bottom, + decoration: const BoxDecoration( + color: Color(0xFFDAE4F7), + borderRadius: BorderRadius.only( + topRight: Radius.circular(24.0), + topLeft: Radius.circular(24.0), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(vertical: 18), + child: Container( + decoration: const BoxDecoration( + color: Color(0xFFB9C8E5), + borderRadius: BorderRadius.only( + topRight: Radius.circular(2.0), + topLeft: Radius.circular(2.0), + ), + ), + width: 40, + height: 4, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + right: 14, left: 14, bottom: 24), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(16), + ), + child: QwIDLoginWidget( + onLogin: (String token) { + setState(() { + longLivedToken = token; + }); + Navigator.of(context).pop(); + }, + ), + ), + ), + ), + ) + ], + ), + ); + }, + ).then((value) { + // After closing the modal bottom sheet, check if the code is set + if (longLivedToken.isNotEmpty) { + // Call your API after retrieving the code + settingsProvider.update( + cloudSyncToken: longLivedToken, + store: true, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('login_successful'.i18n))); + } + }); + }, + trailingDivider: true, + title: Text( + "qwit_sign_in".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity( + settingsProvider.gradeOpeningFun ? .95 : .25), + ), + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + SwitchListTile( + value: settingsProvider.cloudSyncEnabled, + onChanged: (value) { + settingsProvider.update( + cloudSyncEnabled: value, + store: true, + ); + }, + title: Text("cloud_sync_enabled".i18n), + ), + PanelButton( + padding: const EdgeInsets.only(left: 14.0, right: 6.0), + onPressed: () async { + if (settingsProvider.cloudSyncToken.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('sign_in_first'.i18n), + ), + ); + return; + } else { + FilcAPI.cloudSync( + { + "settings": settingsProvider.toMap(), + // "device_ids": [ + // settingsProvider.xFilcId, + // ], + // "refilc_plus_id": settingsProvider.plusSessionId, + }, + settingsProvider.cloudSyncToken, + ).then((response) { + if (response == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('sync_failed'.i18n), + ), + ); + return; + } + + CloudSyncData cloudSyncData = CloudSyncData.fromJson( + response['data']['cloud_sync_data']); + + settingsProvider.updateFromMap( + map: cloudSyncData.settings, + store: true, + ); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('sync_successful'.i18n), + ), + ); + }); + } + }, + trailingDivider: true, + title: Text( + "sync_now".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity( + settingsProvider.gradeOpeningFun ? .95 : .25), + ), + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart index 35c63b1..c6904b2 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart @@ -32,6 +32,8 @@ extension SettingsLocalization on String { "understand": "I understand", "theme_share_failed": "An error occurred while sharing the theme.", "theme_share_ratelimit": "You can only share 1 theme per minute.", + // cloud sync + "cloud_sync": "Cloud Sync", }, "hu_hu": { "general": "Általános", @@ -62,6 +64,8 @@ extension SettingsLocalization on String { "understand": "Értem", "theme_share_failed": "Hiba történt a téma megosztása közben.", "theme_share_ratelimit": "Csak 1 témát oszthatsz meg percenként.", + // cloud sync + "cloud_sync": "Felhő szinkronizálás", }, "de_de": { "general": "Allgemeine", @@ -93,6 +97,8 @@ extension SettingsLocalization on String { "theme_share_failed": "Beim Teilen des Themas ist ein Fehler aufgetreten.", "theme_share_ratelimit": "Sie können nur 1 Thema pro Minute teilen.", + // cloud sync + "cloud_sync": "Cloud-Synchronisierung", }, }; From afcff10862ff67c4680a6c56a369ca002c768a7e Mon Sep 17 00:00:00 2001 From: Kima Date: Mon, 2 Dec 2024 23:13:26 +0100 Subject: [PATCH 8/8] some progress in cloud sync and paypal support almost done --- refilc/lib/api/client.dart | 2 +- refilc/lib/database/init.dart | 1 + refilc/lib/models/settings.dart | 60 +++++++++++++------ .../lib/plus/components/plan_card.dart | 47 +++++++++++++-- .../lib/plus/plus_screen.i18n.dart | 21 ++++++- .../lib/screens/login/qwid_login.dart | 2 +- .../settings/submenu/cloud_sync_screen.dart | 4 +- refilc_plus | 2 +- 8 files changed, 108 insertions(+), 31 deletions(-) diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index 93bfd7a..73f079f 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -395,7 +395,7 @@ class FilcAPI { } // cloud sync - static Future cloudSync(Map data, String token) async { + static Future cloudSync(Map data, String token) async { try { var client = http.Client(); diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index a693315..75826e6 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -58,6 +58,7 @@ const settingsDB = DatabaseStruct("settings", { "unseen_new_features": String, "cloud_sync_enabled": int, "cloud_sync_token": String, + "local_updated_at": String, // quick settings "q_timetable_lesson_num": int, "q_timetable_sub_tiles": int, "q_subjects_sub_tiles": int, diff --git a/refilc/lib/models/settings.dart b/refilc/lib/models/settings.dart index f05ff87..bc75a50 100644 --- a/refilc/lib/models/settings.dart +++ b/refilc/lib/models/settings.dart @@ -111,6 +111,7 @@ class SettingsProvider extends ChangeNotifier { List _unseenNewFeatures; bool _cloudSyncEnabled; String _cloudSyncToken; + DateTime _updatedAt; // quick settings bool _qTimetableLessonNum; bool _qTimetableSubTiles; @@ -188,6 +189,7 @@ class SettingsProvider extends ChangeNotifier { required List unseenNewFeatures, required bool cloudSyncEnabled, required String cloudSyncToken, + required DateTime updatedAt, required bool qTimetableLessonNum, required bool qTimetableSubTiles, required bool qSubjectsSubTiles, @@ -262,6 +264,7 @@ class SettingsProvider extends ChangeNotifier { _unseenNewFeatures = unseenNewFeatures, _cloudSyncEnabled = cloudSyncEnabled, _cloudSyncToken = cloudSyncToken, + _updatedAt = updatedAt, _qTimetableLessonNum = qTimetableLessonNum, _qTimetableSubTiles = qTimetableSubTiles, _qSubjectsSubTiles = qSubjectsSubTiles; @@ -355,6 +358,7 @@ class SettingsProvider extends ChangeNotifier { unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast(), cloudSyncEnabled: map['cloud_sync_enabled'] == 1, cloudSyncToken: map['cloud_sync_token'], + updatedAt: DateTime.tryParse(map['local_updated_at']) ?? DateTime.now(), qTimetableLessonNum: map['q_timetable_lesson_num'] == 1, qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1, qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1, @@ -436,6 +440,7 @@ class SettingsProvider extends ChangeNotifier { "unseen_new_features": jsonEncode(_unseenNewFeatures), "cloud_sync_enabled": _cloudSyncEnabled ? 1 : 0, "cloud_sync_token": _cloudSyncToken, + "local_updated_at": _updatedAt.toIso8601String(), "q_timetable_lesson_num": _qTimetableLessonNum ? 1 : 0, "q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0, "q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0, @@ -521,6 +526,7 @@ class SettingsProvider extends ChangeNotifier { unseenNewFeatures: ['grade_exporting'], cloudSyncEnabled: false, cloudSyncToken: '', + updatedAt: DateTime.now(), qTimetableLessonNum: true, qTimetableSubTiles: true, qSubjectsSubTiles: true, @@ -597,6 +603,7 @@ class SettingsProvider extends ChangeNotifier { List get unseenNewFeatures => _unseenNewFeatures; bool get cloudSyncEnabled => _cloudSyncEnabled; String get cloudSyncToken => _cloudSyncToken; + DateTime get updatedAt => _updatedAt; bool get qTimetableLessonNum => _qTimetableLessonNum; bool get qTimetableSubTiles => _qTimetableSubTiles; bool get qSubjectsSubTiles => _qSubjectsSubTiles; @@ -885,6 +892,8 @@ class SettingsProvider extends ChangeNotifier { if (qSubjectsSubTiles != null && qSubjectsSubTiles != _qSubjectsSubTiles) { _qSubjectsSubTiles = qSubjectsSubTiles; } + // change updated at time + _updatedAt = DateTime.now(); // store or not if (store) await _database?.store.storeSettings(this); notifyListeners(); @@ -894,19 +903,22 @@ class SettingsProvider extends ChangeNotifier { required Map map, bool store = true, }) async { + print(map); + await update( store: store, language: map["language"], - startPage: Pages.values[map["start_page"]], + startPage: Pages.values[map["start_page"] ?? _startPage.index], rounding: map["rounding"], - theme: ThemeMode.values[map["theme"]], - accentColor: AccentColor.values[map["accent_color"]], + theme: ThemeMode.values[map["theme"] ?? _theme.index], + accentColor: + AccentColor.values[map["accent_color"] ?? _accentColor.index], gradeColors: [ - Color(map["grade_color1"]), - Color(map["grade_color2"]), - Color(map["grade_color3"]), - Color(map["grade_color4"]), - Color(map["grade_color5"]), + Color(map["grade_color1"] ?? _gradeColors[0].value), + Color(map["grade_color2"] ?? _gradeColors[1].value), + Color(map["grade_color3"] ?? _gradeColors[2].value), + Color(map["grade_color4"] ?? _gradeColors[3].value), + Color(map["grade_color5"] ?? _gradeColors[4].value), ], newsEnabled: map["news"] == 1, seenNews: map["seen_news"], @@ -918,11 +930,13 @@ class SettingsProvider extends ChangeNotifier { notificationsBitfield: map["notifications_bitfield"], notificationPollInterval: map["notification_poll_interval"], developerMode: map["developer_mode"] == 1, - vibrate: VibrationStrength.values[map["vibration_strength"]], + vibrate: + VibrationStrength.values[map["vibration_strength"] ?? _vibrate.index], abWeeks: map["ab_weeks"] == 1, swapABweeks: map["swap_ab_weeks"] == 1, - updateChannel: UpdateChannel.values[map["update_channel"]], - config: Config.fromJson(jsonDecode(map["config"])), + updateChannel: + UpdateChannel.values[map["update_channel"] ?? _updateChannel.index], + config: Config.fromJson(jsonDecode(map["config"] ?? "{}")), xFilcId: map["x_filc_id"], analyticsEnabled: map["analytics_enabled"] == 1, graphClassAvg: map["graph_class_avg"] == 1, @@ -933,13 +947,19 @@ class SettingsProvider extends ChangeNotifier { gradeOpeningFun: map["grade_opening_fun"] == 1, iconPack: Map.fromEntries( IconPack.values.map((e) => MapEntry(e.name, e)))[map["icon_pack"]]!, - customAccentColor: Color(map["custom_accent_color"]), - customBackgroundColor: Color(map["custom_background_color"]), - customHighlightColor: Color(map["custom_highlight_color"]), - customIconColor: Color(map["custom_icon_color"]), - customTextColor: Color(map["custom_text_color"]), + customAccentColor: + Color(map["custom_accent_color"] ?? _customAccentColor.value), + customBackgroundColor: + Color(map["custom_background_color"] ?? _customBackgroundColor.value), + customHighlightColor: + Color(map["custom_highlight_color"] ?? _customHighlightColor.value), + customIconColor: + Color(map["custom_icon_color"] ?? _customIconColor.value), + customTextColor: + Color(map["custom_text_color"] ?? _customTextColor.value), shadowEffect: map["shadow_effect"] == 1, - premiumScopes: jsonDecode(map["premium_scopes"]).cast(), + premiumScopes: + jsonDecode(map["premium_scopes"] ?? _premiumScopes).cast(), premiumAccessToken: map["premium_token"], premiumLogin: map["premium_login"], lastAccountId: map["last_account_id"], @@ -947,7 +967,8 @@ class SettingsProvider extends ChangeNotifier { renamedSubjectsItalics: map["renamed_subjects_italics"] == 1, renamedTeachersEnabled: map["renamed_teachers_enabled"] == 1, renamedTeachersItalics: map["renamed_teachers_italics"] == 1, - liveActivityColor: Color(map["live_activity_color"]), + liveActivityColor: + Color(map["live_activity_color"] ?? _liveActivityColor), welcomeMessage: map["welcome_message"], appIcon: map["app_icon"], currentThemeId: map['current_theme_id'], @@ -970,7 +991,8 @@ class SettingsProvider extends ChangeNotifier { newColors: map['new_colors'] == 1, uwuMode: map['uwu_mode'] == 1, newPopups: map['new_popups'] == 1, - unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast(), + unseenNewFeatures: + jsonDecode(map["unseen_new_features"] ?? "[]").cast(), cloudSyncEnabled: map['cloud_sync_enabled'] == 1, cloudSyncToken: map['cloud_sync_token'], qTimetableLessonNum: map['q_timetable_lesson_num'] == 1, diff --git a/refilc_mobile_ui/lib/plus/components/plan_card.dart b/refilc_mobile_ui/lib/plus/components/plan_card.dart index f8aba19..a672de0 100644 --- a/refilc_mobile_ui/lib/plus/components/plan_card.dart +++ b/refilc_mobile_ui/lib/plus/components/plan_card.dart @@ -98,11 +98,48 @@ class PlusPlanCard extends StatelessWidget { onTap: () { // pop dialog Navigator.of(context).pop(); - // start payment process - Navigator.of(context) - .push(MaterialPageRoute(builder: (context) { - return PremiumActivationView(product: id); - })); + // show payment option selector + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0)), + title: Text('payment_method'.i18n), + content: Text('select_payment_method'.i18n), + actions: [ + ActionButton( + label: "stripe".i18n, + onTap: () { + // pop dialog + Navigator.of(context).pop(); + // start payment process + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) { + return PremiumActivationView( + product: id, + paymentProvider: "stripe", + ); + })); + }, + ), + ActionButton( + label: "paypal".i18n, + onTap: () { + // pop dialog + Navigator.of(context).pop(); + // start payment process + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) { + return PremiumActivationView( + product: id, + paymentProvider: "paypal", + ); + })); + }, + ), + ], + ), + ); }, ), ], diff --git a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart index 902ba64..ac0db01 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart @@ -47,11 +47,16 @@ extension SettingsLocalization on String { "rfp_16": "Private leaks and informations about upcoming features", "rfp_17": "Grade exporting", "rfp_18": "Viewing exported grades", - // docs popup + // docs and payment method popup "docs": "Documents", "docs_acceptance": "By pressing the \"Next\" button, you accept reFilc's Terms and Conditions for subscriptions (available at the following link: filc.one/pay-terms) and our Privacy Policy (available at the following link: filc.one/pay-privacy).", "next": "Next", + "payment_method": "Payment Method", + "select_payment_method": + "Please select a preferred payment method! Credit card payments are handled by Stripe, which also supports Apple Pay, Google Pay and Revolut Pay.", + "stripe": "Credit Card", + "paypal": "PayPal", // other "and": " and ", "every": "Every ", @@ -106,11 +111,16 @@ extension SettingsLocalization on String { "rfp_16": "Privát betekintések és információk közelgő újításokról", "rfp_17": "Jegy exportálás", "rfp_18": "Exportált jegyek megtekintése", - // docs popup + // docs and payment method popup "docs": "Dokumentumok", "docs_acceptance": "A \"Tovább\" gombra kattintva elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy).", "next": "Tovább", + "payment_method": "Fizetési mód", + "select_payment_method": + "Kérlek válassz egy fizetési módot! A bankkártyás fizetést a Stripe biztosítja, mely támogat Apple Pay-t, Google Pay-t és Revolut Pay-t is.", + "stripe": "Bankkártya", + "paypal": "PayPal", // other "and": " és ", "every": "Minden ", @@ -167,11 +177,16 @@ extension SettingsLocalization on String { "rfp_16": "Private Leaks und Informationen über kommende Funktionen", "rfp_17": "Notenexport", "rfp_18": "Anzeigen exportierter Noten", - // docs popup + // docs and payment method popup "docs": "Dokumente", "docs_acceptance": "Durch Drücken der Schaltfläche \"Weiter\" akzeptieren Sie die Allgemeinen Geschäftsbedingungen von reFilc für Abonnements (verfügbar unter folgendem Link: filc.one/pay-terms) und unsere Datenschutzrichtlinie (verfügbar unter folgendem Link: filc.one/pay-privacy).", "next": "Weiter", + "payment_method": "Zahlungsmethode", + "select_payment_method": + "Bitte wählen Sie eine bevorzugte Zahlungsmethode aus! Kreditkartenzahlungen werden von Stripe abgewickelt, der auch Apple Pay, Google Pay und Revolut Pay unterstützt.", + "stripe": "Kreditkarte", + "paypal": "PayPal", // other "and": " und ", "every": "Jeder ", diff --git a/refilc_mobile_ui/lib/screens/login/qwid_login.dart b/refilc_mobile_ui/lib/screens/login/qwid_login.dart index cc1428e..a19269e 100644 --- a/refilc_mobile_ui/lib/screens/login/qwid_login.dart +++ b/refilc_mobile_ui/lib/screens/login/qwid_login.dart @@ -103,7 +103,7 @@ class _QwIDLoginWidgetState extends State )) ..loadRequest( Uri.parse( - 'https://qwid.qwit.dev/oauth2/authorize?client_id=c3b871fb-d922-4e23-b94d-b31f294c9253&scope=*&redirect_uri=https://api.refilc.hu/v4/oauth2/callback/app/qwid&response_type=code'), // &institute_code=${widget.selectedSchool} + 'https://qwid.qwit.dev/oauth2/authorize?client_id=99aa103a-0bd7-43e0-8421-3bb0b2f6adb1&scope=*&redirect_uri=https://api.refilc.hu/v4/oauth2/callback/app/qwid&response_type=code'), // &institute_code=${widget.selectedSchool} ); } diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart b/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart index 222ade5..0d9ad67 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/cloud_sync_screen.dart @@ -1,4 +1,6 @@ // import 'package:refilc/models/settings.dart'; +import 'dart:convert'; + import 'package:refilc/api/client.dart'; import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; @@ -193,7 +195,7 @@ class CloudSyncSettingsScreenState extends State { } else { FilcAPI.cloudSync( { - "settings": settingsProvider.toMap(), + "settings": jsonEncode(settingsProvider.toMap()), // "device_ids": [ // settingsProvider.xFilcId, // ], diff --git a/refilc_plus b/refilc_plus index bc6ccd9..800f5ce 160000 --- a/refilc_plus +++ b/refilc_plus @@ -1 +1 @@ -Subproject commit bc6ccd961b8e3edf35d279d32c78113fbd1d7a5a +Subproject commit 800f5ce92daafb002990cb7838e8ce4b28bc1a7d