diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..3f51b0a --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,56 @@ +pipeline { + agent any + + environment { + ANDROID_SDK = '/home/jenkins/flutter_things/android-sdk' + ANDROID_PATH="$ANDROID_SDK/tools:$ANDROID_SDK/platform-tools" + FLUTTER = '/home/jenkins/flutter_things/flutter/bin' + PATH = "$PATH:$ANDROID_PATH:$FLUTTER" + //TODO: need to fix flutter + } + + + stages { + stage('Copy Key Properties') { + steps { + // Copy the key.properties file + sh 'cp /home/jenkins/key.properties refilc/android/key.properties' + } + } + + stage('Flutter Doctor') { + steps { + // Ensure Flutter is set up correctly + sh 'flutter doctor' + } + } + + stage('Dependencies') { + steps { + // Get Flutter dependencies + sh 'cd refilc && flutter pub get' + } + } + + stage('Build') { + steps { + // Build the Flutter project + sh 'cd refilc && flutter build apk --release' + } + } + + stage('Archive') { + steps { + // Archive the APK + archiveArtifacts artifacts: 'build/app/outputs/flutter-apk/app-release.apk', fingerprint: true + } + } + } + + post { + always { + // Clean up workspace after build + cleanWs() + } + } +} diff --git a/refilc/android/app/src/main/res/layout/timetable_item.xml b/refilc/android/app/src/main/res/layout/timetable_item.xml index f057d2d..c72dc65 100644 --- a/refilc/android/app/src/main/res/layout/timetable_item.xml +++ b/refilc/android/app/src/main/res/layout/timetable_item.xml @@ -20,7 +20,7 @@ android:gravity="center" android:text="1." android:textColor="@color/filc" - android:textColorLink="#ff3D7BF4" + android:textColorLink="#ff052460" android:textSize="30sp" android:textStyle="bold" tools:ignore="HardcodedText" /> @@ -110,4 +110,4 @@ android:textColor="@color/white" tools:ignore="HardcodedText" /> - \ No newline at end of file + diff --git a/refilc/android/app/src/main/res/values/colors.xml b/refilc/android/app/src/main/res/values/colors.xml index 955cc10..f1facef 100644 --- a/refilc/android/app/src/main/res/values/colors.xml +++ b/refilc/android/app/src/main/res/values/colors.xml @@ -27,7 +27,7 @@ #ffFFCC00 #40FFD60A #ff34C759 - #ff3D7BF4 + #ff052460 #ff5AC8FA #ff007AFF #ff5856D6 @@ -49,8 +49,8 @@ #ffFFD60A #40FFD60A #ff32D74B - #ff3D7BF4 - #ff3D93F5 + #ff052460 + #ff06348f #ff64D2FF #ff0A84FF #ff5E5CE6 @@ -66,4 +66,4 @@ #FFF06292 #FFBA68C8 #FF22AC9B - \ No newline at end of file + diff --git a/refilc/assets/svg/cover_arts/grid.svg b/refilc/assets/svg/cover_arts/grid.svg new file mode 100644 index 0000000..69e221c --- /dev/null +++ b/refilc/assets/svg/cover_arts/grid.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/refilc/assets/svg/cover_arts/line.svg b/refilc/assets/svg/cover_arts/line.svg new file mode 100644 index 0000000..7436d92 --- /dev/null +++ b/refilc/assets/svg/cover_arts/line.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/refilc/assets/svg/cover_arts/plain.svg b/refilc/assets/svg/cover_arts/plain.svg new file mode 100644 index 0000000..e381afd --- /dev/null +++ b/refilc/assets/svg/cover_arts/plain.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/refilc/assets/svg/cover_arts/vocal.svg b/refilc/assets/svg/cover_arts/vocal.svg new file mode 100644 index 0000000..50b24fe --- /dev/null +++ b/refilc/assets/svg/cover_arts/vocal.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/refilc/assets/svg/mesh_bg.svg b/refilc/assets/svg/mesh_bg.svg deleted file mode 100644 index 5301c50..0000000 --- a/refilc/assets/svg/mesh_bg.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/refilc/ios/livecard/livecard.swift b/refilc/ios/livecard/livecard.swift index f5f27e7..0271d1c 100644 --- a/refilc/ios/livecard/livecard.swift +++ b/refilc/ios/livecard/livecard.swift @@ -52,14 +52,31 @@ struct LockScreenLiveActivityView: View { VStack(alignment: .center) { // Jelenlegi óra VStack { - Text(context.state.index + " " + context.state.title) - .font(.body) - .bold() - .multilineTextAlignment(.center) + if(context.state.title.contains("Az első órádig")) { + Text(context.state.title) + .font(.system(size: 15)) + .bold() + .multilineTextAlignment(.center) + } else if(context.state.title == "Szünet") { + Text(context.state.title) + .font(.body) + .bold() + .padding(.trailing, 90) - Text("Terem: \(context.state.subtitle)") - .italic() - .font(.caption) + } else { + MultilineTextView(text: "\(context.state.index) \(context.state.title)", limit: 25) + .font(.body) + .bold() + .multilineTextAlignment(.center) + } + + //Terem + if (!context.state.subtitle.isEmpty) { + Text(context.state.subtitle) + .italic() + .bold() + .font(.system(size: 13)) + } } // Leírás @@ -69,6 +86,7 @@ struct LockScreenLiveActivityView: View { } // Következő óra + if(context.state.nextSubject != "" && context.state.nextRoom != "") { HStack { Image(systemName: "arrow.right") .resizable() @@ -80,18 +98,24 @@ struct LockScreenLiveActivityView: View { .font(.caption2) } .multilineTextAlignment(.center) + } else { + Spacer(minLength: 5) + Text("Ez az utolsó óra! Kitartást!") + .font(.system(size: 15)) + } + } .padding(15) Spacer() - + // Visszaszámláló Text(timerInterval: context.state.date, countsDown: true) .multilineTextAlignment(.center) .frame(width: 85) .font(.title2) .monospacedDigit() - .padding(.trailing, CGFloat(24)) + .padding(.trailing) } .activityBackgroundTint( context.state.color != "#676767" @@ -109,7 +133,7 @@ struct LiveCardWidget: Widget { LockScreenLiveActivityView(context: context) /// Dynamic Island } dynamicIsland: { context in - + /// Expanded return DynamicIsland { DynamicIslandExpandedRegion(.leading) { @@ -133,39 +157,95 @@ struct LiveCardWidget: Widget { ).progressViewStyle(.circular) } } - DynamicIslandExpandedRegion(.center) { - VStack(alignment: .center) { - Text(context.state.index + context.state.title) - .lineLimit(1) - .font(.body) - .bold() - - Text(context.state.subtitle) - .lineLimit(1) - .font(.subheadline) - Spacer() - - Text(context.state.description) - .lineLimit(2) - .font(.caption) - }.padding(EdgeInsets(top: 0.0, leading: 5.0, bottom: 0.0, trailing: 0.0)) - } + DynamicIslandExpandedRegion(.center) { + VStack(alignment: .center) { + // Első óra előtti expanded DynamicIsland + if(context.state.title.contains("Az első órádig")) { + Text("Az első órád:") + .font(.body) + .bold() + .padding(.trailing, -15) + MultilineTextView(text: "\(context.state.nextSubject)", limit: 25) + .font(.body) + .padding(.trailing, -25) + + Text("Ebben a teremben:") + .font(.body) + .bold() + .padding(.leading, 15) + Text(context.state.nextRoom) + .font(.body) + .padding(.leading, 15) + } else if(context.state.title == "Szünet") { + // Amikor szünet van, expanded DynamicIsland + Text(context.state.title) + .lineLimit(1) + .font(.body) + .bold() + .padding(.leading, 15) + + Spacer(minLength: 5) + Text("Következő óra és terem:") + .font(.system(size: 13)) + .padding(.leading, 25) + Text(context.state.nextSubject) + .font(.caption) + .padding(.leading, 15) + Text(context.state.nextRoom) + .font(.caption2) + .padding(.leading, 15) + + } else { + // Amikor óra van, expanded DynamicIsland + MultilineTextView(text: "\(context.state.index) \(context.state.title)", limit: 25) + .lineLimit(1) + .font(.body) + .bold() + .padding(.trailing, -35) + + Text(context.state.subtitle) + .lineLimit(1) + .italic() + .bold() + .font(.system(size: 13)) + .padding(.trailing, -50) + + Spacer(minLength: 5) + + if(context.state.nextRoom != "" && context.state.nextSubject != "") { + Text("Következő óra és terem:") + .font(.system(size: 14)) + .padding(.trailing, -35) + Spacer(minLength: 2) + Text(context.state.nextSubject) + .modifier(DynamicFontSizeModifier(text: context.state.nextSubject)) + .padding(.trailing, -35) + Text(context.state.nextRoom) + // ignore: based on nextSubject characters, I check that the font size of the room is the same as the next subject. + .modifier(DynamicFontSizeModifier(text: context.state.nextSubject)) + .padding(.trailing, -35) + } else { + Text("Ez az utolsó óra! Kitartást!") + .font(.system(size: 14)) + .padding(.trailing, -30) + } + } + + + }.padding(EdgeInsets(top: 0.0, leading: 5.0, bottom: 0.0, trailing: 0.0)) + } + /// Compact } compactLeading: { - Label { - Text(context.state.title) - } icon: { - Image(systemName: context.state.icon) - } - .font(.caption2) + Image(systemName: context.state.icon) } compactTrailing: { Text(timerInterval: context.state.date, countsDown: true) .multilineTextAlignment(.center) .frame(width: 40) .font(.caption2) - + /// Collapsed } minimal: { VStack(alignment: .center, content: { @@ -191,7 +271,59 @@ struct LiveCardWidget: Widget { context.state.color != "#676767" ? Color(hex: context.state.color) : Color.clear - ) + ) + } + } +} + +struct MultilineTextView: View { + var text: String + var limit: Int = 20 // default is 20 character + + var body: some View { + let words = text.split(separator: " ") + var currentLine = "" + var lines: [String] = [] + + for word in words { + if (currentLine.count + word.count + 1) > limit { + lines.append(currentLine) + currentLine = "" + } + if !currentLine.isEmpty { + currentLine += " " + } + currentLine += word + } + if !currentLine.isEmpty { + lines.append(currentLine) + } + + return VStack(alignment: .center) { + ForEach(lines, id: \.self) { line in + Text(line) + } + Spacer(minLength: 1) + } + } +} + +struct DynamicFontSizeModifier: ViewModifier { + var text: String + + func body(content: Content) -> some View { + content + .font(.system(size: fontSize(for: text))) + } + + private func fontSize(for text: String) -> CGFloat { + let length = text.count + if length < 10 { + return 12 + } else if length < 20 { + return 12 + } else { + return 11 } } } diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index f86428e..83fe55a 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -336,6 +336,8 @@ class FilcAPI { if (res.statusCode == 200) { return (jsonDecode(res.body) as Map); + } else if (res.statusCode == 404) { + return {"public_id": ""}; } else { throw "HTTP ${res.statusCode}: ${res.body}"; } diff --git a/refilc/lib/api/providers/live_card_provider.dart b/refilc/lib/api/providers/live_card_provider.dart index 1d581db..d1d2922 100644 --- a/refilc/lib/api/providers/live_card_provider.dart +++ b/refilc/lib/api/providers/live_card_provider.dart @@ -88,24 +88,24 @@ class LiveCardProvider extends ChangeNotifier { case LiveCardState.morning: return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": nextLesson != null ? SubjectIcon.resolveName(subject: nextLesson?.subject) : "book", - "title": "Első órádig:", + "title": "Jó reggelt! Az első órádig:", "subtitle": "", "description": "", "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - - (_delay.inMilliseconds)) - .toString() + (_delay.inMilliseconds)) + .toString() : "", "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject).capital() + ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; @@ -113,24 +113,24 @@ class LiveCardProvider extends ChangeNotifier { case LiveCardState.afternoon: return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": nextLesson != null ? SubjectIcon.resolveName(subject: nextLesson?.subject) : "book", - "title": "Első órádig:", + "title": "Jó napot! Az első órádig:", "subtitle": "", "description": "", "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - - (_delay.inMilliseconds)) - .toString() + (_delay.inMilliseconds)) + .toString() : "", "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject).capital() + ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; @@ -138,24 +138,24 @@ class LiveCardProvider extends ChangeNotifier { case LiveCardState.night: return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": nextLesson != null ? SubjectIcon.resolveName(subject: nextLesson?.subject) : "book", - "title": "Első órádig:", + "title": "Jó estét! Az első órádig:", "subtitle": "", "description": "", "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - - (_delay.inMilliseconds)) - .toString() + (_delay.inMilliseconds)) + .toString() : "", "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject).capital() + ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; @@ -163,28 +163,28 @@ class LiveCardProvider extends ChangeNotifier { case LiveCardState.duringLesson: return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book", "index": - currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "", + currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "", "title": currentLesson != null ? currentLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: currentLesson?.subject) - .capital() + ShortSubject.resolve(subject: currentLesson?.subject) + .capital() : "", - "subtitle": currentLesson?.room.replaceAll("_", " ") ?? "", + "subtitle": "Terem: ${currentLesson?.room.replaceAll("_", " ") ?? ""}", "description": currentLesson?.description ?? "", "startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject).capital() + ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; @@ -200,23 +200,23 @@ class LiveCardProvider extends ChangeNotifier { return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": iconFloorMap[diff] ?? "cup.and.saucer", "title": "Szünet", "description": "go $diff".i18n.fill([ diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room ]), "startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": (nextLesson != null - ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject) - .capital() - : "") + ? nextLesson?.subject.renamedTo ?? + ShortSubject.resolve(subject: nextLesson?.subject) + .capital() + : "") .capital(), "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", "index": "", @@ -243,8 +243,8 @@ class LiveCardProvider extends ChangeNotifier { DateTime now = _now().add(_delay); if ((currentState == LiveCardState.morning || - currentState == LiveCardState.afternoon || - currentState == LiveCardState.night) && + currentState == LiveCardState.afternoon || + currentState == LiveCardState.night) && storeFirstRunDate == null) { storeFirstRunDate = now; } @@ -253,9 +253,9 @@ class LiveCardProvider extends ChangeNotifier { // Filter label lessons #128 today = today .where((lesson) => - lesson.status?.name != "Elmaradt" && - lesson.subject.id != '' && - !lesson.isEmpty) + lesson.status?.name != "Elmaradt" && + lesson.subject.id != '' && + !lesson.isEmpty) .toList(); if (today.isNotEmpty) { @@ -263,7 +263,7 @@ class LiveCardProvider extends ChangeNotifier { today.sort((a, b) => a.start.compareTo(b.start)); final _lesson = today.firstWhere( - (l) => l.start.isBefore(now) && l.end.isAfter(now), + (l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({})); if (_lesson.start.year != 0) { @@ -328,7 +328,7 @@ class LiveCardProvider extends ChangeNotifier { hasActivityStarted = true; } else if (!hasActivityStarted && ((currentState == LiveCardState.duringLesson && - currentLesson != null) || + currentLesson != null) || currentState == LiveCardState.duringBreak)) { debugPrint("Óra van, vagy szünet, de nincs LiveActivity. létrehozás..."); PlatformChannel.createLiveActivity(toMap()); @@ -362,7 +362,12 @@ class LiveCardProvider extends ChangeNotifier { } //END - if (hasActivityStarted && + if ((currentState == LiveCardState.afternoon || currentState == LiveCardState.morning || currentState == LiveCardState.night) && hasActivityStarted && nextLesson != null && + nextLesson!.start.difference(now).inMinutes > 60) { + debugPrint("Több, mint 1 óra van az első óráig. Befejezés..."); + PlatformChannel.endLiveActivity(); + hasActivityStarted = false; + } else if (hasActivityStarted && !hasDayEnd && nextLesson == null && now.isAfter(prevLesson!.end)) { @@ -384,4 +389,4 @@ class LiveCardProvider extends ChangeNotifier { List _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? []) .where((l) => _sameDate(l.date, _now())) .toList(); -} +} \ No newline at end of file diff --git a/refilc/lib/api/providers/self_note_provider.dart b/refilc/lib/api/providers/self_note_provider.dart index 3a095b7..4be70a5 100644 --- a/refilc/lib/api/providers/self_note_provider.dart +++ b/refilc/lib/api/providers/self_note_provider.dart @@ -7,17 +7,23 @@ import 'package:provider/provider.dart'; class SelfNoteProvider with ChangeNotifier { late List _notes; + late List _todoItems; late BuildContext _context; + List get notes => _notes; + List get todos => _todoItems; SelfNoteProvider({ List initialNotes = const [], + List initialTodoItems = const [], required BuildContext context, }) { _notes = List.castFrom(initialNotes); + _todoItems = List.castFrom(initialTodoItems); _context = context; if (_notes.isEmpty) restore(); + if (_todoItems.isEmpty) restoreTodo(); } // restore self notes from db @@ -38,6 +44,24 @@ class SelfNoteProvider with ChangeNotifier { } } + // restore todo items from db + Future restoreTodo() async { + String? userId = Provider.of(_context, listen: false).id; + + // await Provider.of(_context, listen: false) + // .userStore + // .storeSelfNotes([], userId: userId!); + + // load self notes from db + if (userId != null) { + var dbTodo = await Provider.of(_context, listen: false) + .userQuery + .getTodoItems(userId: userId); + _todoItems = dbTodo; + notifyListeners(); + } + } + // fetches fresh data from api (not needed, cuz no api for that) // Future fetch() async { // } @@ -54,4 +78,17 @@ class SelfNoteProvider with ChangeNotifier { _notes = notes; notifyListeners(); } + + // store todo items in db + Future storeTodo(List todos) async { + User? user = Provider.of(_context, listen: false).user; + if (user == null) throw "Cannot store Self Notes for User null"; + String userId = user.id; + + await Provider.of(_context, listen: false) + .userStore + .storeSelfTodoItems(todos, userId: userId); + _todoItems = todos; + notifyListeners(); + } } diff --git a/refilc/lib/api/providers/user_provider.dart b/refilc/lib/api/providers/user_provider.dart index a71028c..8d4bc89 100644 --- a/refilc/lib/api/providers/user_provider.dart +++ b/refilc/lib/api/providers/user_provider.dart @@ -23,6 +23,7 @@ class UserProvider with ChangeNotifier { String? get nickname => user?.nickname; String get picture => user?.picture ?? ""; String? get displayName => user?.displayName; + int? get gradeStreak => user?.gradeStreak; final SettingsProvider _settings; diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index 23960a9..a8c820d 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -45,6 +45,7 @@ const settingsDB = DatabaseStruct("settings", { // more "show_breaks": int, "font_family": String, + "title_only_font": int, "plus_session_id": String, "cal_sync_room_location": String, "cal_sync_show_exams": int, "cal_sync_show_teacher": int, "cal_sync_renamed": int, @@ -52,6 +53,7 @@ const settingsDB = DatabaseStruct("settings", { "nav_shadow": int, "new_colors": int, "uwu_mode": int, + "new_popups": int, // quick settings "q_timetable_lesson_num": int, "q_timetable_sub_tiles": int, "q_subjects_sub_tiles": int, @@ -61,7 +63,8 @@ const settingsDB = DatabaseStruct("settings", { const usersDB = DatabaseStruct("users", { "id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int, - "nickname": String, "picture": String // premium only + "nickname": String, "picture": String, // premium only (it's now plus btw) + "grade_streak": int, }); const userDataDB = DatabaseStruct("user_data", { "id": String, "grades": String, "timetable": String, "exams": String, @@ -84,12 +87,13 @@ const userDataDB = DatabaseStruct("user_data", { "goal_befores": String, "goal_pin_dates": String, // todo and notes - "todo_items": String, "self_notes": String, + "todo_items": String, "self_notes": String, "self_todo": String, // v5 shit "roundings": String, "grade_rarities": String, "linked_accounts": String, "custom_lesson_desc": String, + "watch_data": String, }); Future createTable(Database db, DatabaseStruct struct) => @@ -129,7 +133,12 @@ Future initDB(DatabaseProvider database) async { await migrateDB( db, struct: usersDB, - defaultValues: {"role": 0, "nickname": "", "picture": ""}, + defaultValues: { + "role": 0, + "nickname": "", + "picture": "", + "grade_streak": 0 + }, ); await migrateDB(db, struct: userDataDB, defaultValues: { "grades": "[]", "timetable": "[]", "exams": "[]", "homework": "[]", @@ -152,12 +161,13 @@ Future initDB(DatabaseProvider database) async { "goal_befores": "{}", "goal_pin_dates": "{}", // todo and notes - "todo_items": "{}", "self_notes": "[]", + "todo_items": "{}", "self_notes": "[]", "self_todo": "[]", // v5 shit "roundings": "{}", "grade_rarities": "{}", "linked_accounts": "[]", "custom_lesson_desc": "{}", + "watch_data": "{}", }); } catch (error) { print("ERROR: migrateDB: $error"); diff --git a/refilc/lib/database/query.dart b/refilc/lib/database/query.dart index e899e6d..741a253 100644 --- a/refilc/lib/database/query.dart +++ b/refilc/lib/database/query.dart @@ -317,6 +317,18 @@ class UserDatabaseQuery { return selfNotes; } + Future> getTodoItems({required String userId}) async { + List userData = + await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return []; + String? todoItemsJson = userData.elementAt(0)["self_todo"] as String?; + if (todoItemsJson == null) return []; + List todoItems = (jsonDecode(todoItemsJson) as List) + .map((e) => TodoItem.fromJson(e)) + .toList(); + return todoItems; + } + // v5 Future> getRoundings({required String userId}) async { List userData = diff --git a/refilc/lib/database/store.dart b/refilc/lib/database/store.dart index 6132780..46929c0 100644 --- a/refilc/lib/database/store.dart +++ b/refilc/lib/database/store.dart @@ -196,6 +196,13 @@ class UserDatabaseStore { where: "id = ?", whereArgs: [userId]); } + Future storeSelfTodoItems(List todoItems, + {required String userId}) async { + String todoItemsJson = jsonEncode(todoItems.map((e) => e.json).toList()); + await db.update("user_data", {"self_todo": todoItemsJson}, + where: "id = ?", whereArgs: [userId]); + } + // v5 Future storeRoundings(Map roundings, {required String userId}) async { diff --git a/refilc/lib/helpers/notification_helper.dart b/refilc/lib/helpers/notification_helper.dart index 7b3bac5..d909578 100644 --- a/refilc/lib/helpers/notification_helper.dart +++ b/refilc/lib/helpers/notification_helper.dart @@ -212,7 +212,7 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k ), notificationDetails, payload: "grades"); - } else if (settingsProvider.gradeOpeningFun) { + } else if (settingsProvider.gradeOpeningFun) { // if surprise grades are enabled, show a notification without the grade await flutterLocalNotificationsPlugin.show( grade.id.hashCode, @@ -485,9 +485,10 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k lesson.lessonIndex, lesson.name, dayTitle(lesson.date), - lesson.substituteTeacher!.isRenamed - ? lesson.substituteTeacher!.renamedTo! - : lesson.substituteTeacher!.name + ((lesson.substituteTeacher?.isRenamed ?? false) + ? lesson.substituteTeacher?.renamedTo! + : lesson.substituteTeacher?.name) ?? + '', ], ), notificationDetails, @@ -505,9 +506,10 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k dayTitle(lesson.date), lesson.lessonIndex, lesson.name, - lesson.substituteTeacher!.isRenamed - ? lesson.substituteTeacher!.renamedTo! - : lesson.substituteTeacher!.name + ((lesson.substituteTeacher?.isRenamed ?? false) + ? lesson.substituteTeacher?.renamedTo! + : lesson.substituteTeacher?.name) ?? + '', ], ), notificationDetails, @@ -525,9 +527,10 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k lesson.lessonIndex, lesson.name, dayTitle(lesson.date), - lesson.substituteTeacher!.isRenamed - ? lesson.substituteTeacher!.renamedTo! - : lesson.substituteTeacher!.name + ((lesson.substituteTeacher?.isRenamed ?? false) + ? lesson.substituteTeacher?.renamedTo! + : lesson.substituteTeacher?.name) ?? + '', ], ), notificationDetails, @@ -608,9 +611,10 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k lesson.lessonIndex, lesson.name, dayTitle(lesson.date), - lesson.substituteTeacher!.isRenamed - ? lesson.substituteTeacher!.renamedTo! - : lesson.substituteTeacher!.name + ((lesson.substituteTeacher?.isRenamed ?? false) + ? lesson.substituteTeacher?.renamedTo! + : lesson.substituteTeacher?.name) ?? + '', ], ), notificationDetails, @@ -629,9 +633,10 @@ nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén k dayTitle(lesson.date), lesson.lessonIndex, lesson.name, - lesson.substituteTeacher!.isRenamed - ? lesson.substituteTeacher!.renamedTo! - : lesson.substituteTeacher!.name + ((lesson.substituteTeacher?.isRenamed ?? false) + ? lesson.substituteTeacher?.renamedTo! + : lesson.substituteTeacher?.name) ?? + '', ], ), notificationDetails, diff --git a/refilc/lib/helpers/subject.dart b/refilc/lib/helpers/subject.dart index 7aa8e9d..24607b5 100644 --- a/refilc/lib/helpers/subject.dart +++ b/refilc/lib/helpers/subject.dart @@ -292,3 +292,105 @@ class ShortSubject { return subject?.name.capital() ?? subjectName?.capital() ?? "?"; } } + +// new v5 thingie +class SubjectBooklet { + // static String resolveName({GradeSubject? subject, String? subjectName}) => + // _resolve(subject: subject, subjectName: subjectName).name; + static String resolveVariant( + {GradeSubject? subject, + String? subjectName, + required BuildContext context}) => + _resolve(subject: subject, subjectName: subjectName); + + static String _resolve({GradeSubject? subject, String? subjectName}) { + assert(!(subject == null && subjectName == null)); + + String name = (subject?.name ?? subjectName ?? "") + .toLowerCase() + .specialChars() + .trim(); + String category = + subject?.category.description.toLowerCase().specialChars() ?? ""; + + String basePath = "assets/svg/cover_arts"; + + // todo: check for categories + if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") { + return "$basePath/grid.svg"; + } else if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("irodalom").hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("tor(i|tenelem)").hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("foldrajz").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("rajz|muvtori|muveszet|vizualis").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("fizika").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) { + return "$basePath/vocal.svg"; + } else if (RegExp("^tes(i|tneveles)|sport|edzeselmelet").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("kemia").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("biologia").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp( + "kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret") + .hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("(hit|erkolcs)tan|vallas|etika|bibliaismeret") + .hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("penzugy").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("informatika|szoftver|iroda|digitalis").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("prog").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("halozat").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("szinhaz").hasMatch(name)) { + return "$basePath/vocal.svg"; + } else if (RegExp("film|media").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("elektro(tech)?nika").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("gepesz|mernok|ipar").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("technika").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("tanc").hasMatch(name)) { + return "$basePath/vocal.svg"; + } else if (RegExp("filozofia").hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("osztaly(fonoki|kozosseg)|kozossegi|neveles") + .hasMatch(name) || + name == "ofo") { + return "$basePath/plain.svg"; + } else if (RegExp("gazdasag").hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("szorgalom").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("magatartas").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp( + "angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv") + .hasMatch(name)) { + return "$basePath/line.svg"; + } else if (RegExp("linux").hasMatch(name)) { + return "$basePath/plain.svg"; + } else if (RegExp("adatbazis").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("asztali alkalmazasok").hasMatch(name)) { + return "$basePath/grid.svg"; + } else if (RegExp("projekt").hasMatch(name)) { + return "$basePath/plain.svg"; + } + + return "$basePath/plain.svg"; + } +} diff --git a/refilc/lib/models/self_note.dart b/refilc/lib/models/self_note.dart index bd86a1d..cbaf42b 100644 --- a/refilc/lib/models/self_note.dart +++ b/refilc/lib/models/self_note.dart @@ -33,3 +33,37 @@ class SelfNote { 'note_type': noteType == NoteType.image ? 'image' : 'text', }; } + +class TodoItem { + String id; + String title; + String content; + bool done; + + Map? json; + + TodoItem({ + required this.id, + required this.title, + required this.content, + required this.done, + this.json, + }); + + factory TodoItem.fromJson(Map json) { + return TodoItem( + id: json['id'], + title: json['title'], + content: json['content'], + done: json['done'], + json: json, + ); + } + + get toJson => { + 'id': id, + 'title': title, + 'content': content, + 'done': done, + }; +} diff --git a/refilc/lib/models/settings.dart b/refilc/lib/models/settings.dart index 72d23f1..298e704 100644 --- a/refilc/lib/models/settings.dart +++ b/refilc/lib/models/settings.dart @@ -96,6 +96,7 @@ class SettingsProvider extends ChangeNotifier { // more bool _showBreaks; String _fontFamily; + bool _titleOnlyFont; String _plusSessionId; String _calSyncRoomLocation; bool _calSyncShowExams; @@ -105,6 +106,7 @@ class SettingsProvider extends ChangeNotifier { bool _navShadow; bool _newColors; bool _uwuMode; + bool _newPopups; // quick settings bool _qTimetableLessonNum; bool _qTimetableSubTiles; @@ -167,6 +169,7 @@ class SettingsProvider extends ChangeNotifier { required String pinSetNotify, required String pinSetExtras, required String fontFamily, + required bool titleOnlyFont, required String plusSessionId, required String calSyncRoomLocation, required bool calSyncShowExams, @@ -176,6 +179,7 @@ class SettingsProvider extends ChangeNotifier { required bool navShadow, required bool newColors, required bool uwuMode, + required bool newPopups, required bool qTimetableLessonNum, required bool qTimetableSubTiles, required bool qSubjectsSubTiles, @@ -235,6 +239,7 @@ class SettingsProvider extends ChangeNotifier { _pinSetNotify = pinSetNotify, _pinSetExtras = pinSetExtras, _fontFamily = fontFamily, + _titleOnlyFont = titleOnlyFont, _plusSessionId = plusSessionId, _calSyncRoomLocation = calSyncRoomLocation, _calSyncShowExams = calSyncShowExams, @@ -244,6 +249,7 @@ class SettingsProvider extends ChangeNotifier { _navShadow = navShadow, _newColors = newColors, _uwuMode = uwuMode, + _newPopups = newPopups, _qTimetableLessonNum = qTimetableLessonNum, _qTimetableSubTiles = qTimetableSubTiles, _qSubjectsSubTiles = qSubjectsSubTiles; @@ -322,6 +328,7 @@ class SettingsProvider extends ChangeNotifier { 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, @@ -331,6 +338,7 @@ class SettingsProvider extends ChangeNotifier { navShadow: map['nav_shadow'] == 1, newColors: map['new_colors'] == 1, uwuMode: map['uwu_mode'] == 1, + newPopups: map['new_popups'] == 1, qTimetableLessonNum: map['q_timetable_lesson_num'] == 1, qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1, qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1, @@ -397,6 +405,7 @@ class SettingsProvider extends ChangeNotifier { "notify_s_pin": _pinSetNotify, "extras_s_pin": _pinSetExtras, "font_family": _fontFamily, + "title_only_font": _titleOnlyFont ? 1 : 0, "plus_session_id": _plusSessionId, "cal_sync_room_location": _calSyncRoomLocation, "cal_sync_show_exams": _calSyncShowExams ? 1 : 0, @@ -406,6 +415,7 @@ class SettingsProvider extends ChangeNotifier { "nav_shadow": _navShadow ? 1 : 0, "new_colors": _newColors ? 1 : 0, "uwu_mode": _uwuMode ? 1 : 0, + "new_popups": _newPopups ? 1 : 0, "q_timetable_lesson_num": _qTimetableLessonNum ? 1 : 0, "q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0, "q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0, @@ -476,6 +486,7 @@ class SettingsProvider extends ChangeNotifier { pinSetNotify: '', pinSetExtras: '', fontFamily: '', + titleOnlyFont: false, plusSessionId: '', calSyncRoomLocation: 'location', calSyncShowExams: true, @@ -485,6 +496,7 @@ class SettingsProvider extends ChangeNotifier { navShadow: true, newColors: true, uwuMode: false, + newPopups: true, qTimetableLessonNum: true, qTimetableSubTiles: true, qSubjectsSubTiles: true, @@ -546,6 +558,7 @@ class SettingsProvider extends ChangeNotifier { String get currentThemeCreator => _currentThemeCreator; bool get showBreaks => _showBreaks; String get fontFamily => _fontFamily; + bool get titleOnlyFont => _titleOnlyFont; String get plusSessionId => _plusSessionId; String get calSyncRoomLocation => _calSyncRoomLocation; bool get calSyncShowExams => _calSyncShowExams; @@ -555,6 +568,7 @@ class SettingsProvider extends ChangeNotifier { bool get navShadow => _navShadow; bool get newColors => _newColors; bool get uwuMode => _uwuMode; + bool get newPopups => _newPopups; bool get qTimetableLessonNum => _qTimetableLessonNum; bool get qTimetableSubTiles => _qTimetableSubTiles; bool get qSubjectsSubTiles => _qSubjectsSubTiles; @@ -612,6 +626,7 @@ class SettingsProvider extends ChangeNotifier { String? currentThemeCreator, bool? showBreaks, String? fontFamily, + bool? titleOnlyFont, String? plusSessionId, String? calSyncRoomLocation, bool? calSyncShowExams, @@ -621,6 +636,7 @@ class SettingsProvider extends ChangeNotifier { bool? navShadow, bool? newColors, bool? uwuMode, + bool? newPopups, bool? qTimetableLessonNum, bool? qTimetableSubTiles, bool? qSubjectsSubTiles, @@ -777,6 +793,9 @@ class SettingsProvider extends ChangeNotifier { if (fontFamily != null && fontFamily != _fontFamily) { _fontFamily = fontFamily; } + if (titleOnlyFont != null && titleOnlyFont != _titleOnlyFont) { + _titleOnlyFont = titleOnlyFont; + } if (plusSessionId != null && plusSessionId != _plusSessionId) { _plusSessionId = plusSessionId; } @@ -806,6 +825,9 @@ class SettingsProvider extends ChangeNotifier { if (uwuMode != null && uwuMode != _uwuMode) { _uwuMode = uwuMode; } + if (newPopups != null && newPopups != _newPopups) { + _newPopups = newPopups; + } if (qTimetableLessonNum != null && qTimetableLessonNum != _qTimetableLessonNum) { _qTimetableLessonNum = qTimetableLessonNum; diff --git a/refilc/lib/models/user.dart b/refilc/lib/models/user.dart index 7f22018..83d15d3 100644 --- a/refilc/lib/models/user.dart +++ b/refilc/lib/models/user.dart @@ -16,8 +16,10 @@ class User { Role role; String nickname; String picture; + int gradeStreak; String get displayName => nickname != '' ? nickname : name; + bool get hasStreak => gradeStreak > 0; User({ String? id, @@ -29,6 +31,7 @@ class User { required this.role, this.nickname = "", this.picture = "", + this.gradeStreak = 0, }) { if (id != null) { this.id = id; @@ -57,6 +60,7 @@ class User { role: Role.values[map["role"] ?? 0], nickname: map["nickname"] ?? "", picture: map["picture"] ?? "", + gradeStreak: map["grade_streak"] ?? 0, ); } diff --git a/refilc/lib/theme/theme.dart b/refilc/lib/theme/theme.dart index e1a9215..1ddd93e 100644 --- a/refilc/lib/theme/theme.dart +++ b/refilc/lib/theme/theme.dart @@ -104,9 +104,10 @@ class AppTheme { brightness: Brightness.light, useMaterial3: true, fontFamily: _defaultFontFamily, - textTheme: - googleFontsMap[settings.fontFamily]?.apply(bodyColor: textColor) ?? - const TextTheme().apply(bodyColor: textColor), + textTheme: !settings.titleOnlyFont + ? (googleFontsMap[settings.fontFamily]?.apply(bodyColor: textColor) ?? + const TextTheme().apply(bodyColor: textColor)) + : null, scaffoldBackgroundColor: backgroundColor, primaryColor: lightColors.filc, dividerColor: const Color(0x00000000), @@ -229,8 +230,9 @@ class AppTheme { brightness: Brightness.dark, useMaterial3: true, fontFamily: _defaultFontFamily, - textTheme: - googleFontsMap[settings.fontFamily]?.apply(bodyColor: textColor), + textTheme: !settings.titleOnlyFont + ? (googleFontsMap[settings.fontFamily]?.apply(bodyColor: textColor)) + : null, scaffoldBackgroundColor: backgroundColor, primaryColor: darkColors.filc, dividerColor: const Color(0x00000000), diff --git a/refilc/lib/ui/flutter_colorpicker/colorpicker.dart b/refilc/lib/ui/flutter_colorpicker/colorpicker.dart index 783f363..58333f9 100644 --- a/refilc/lib/ui/flutter_colorpicker/colorpicker.dart +++ b/refilc/lib/ui/flutter_colorpicker/colorpicker.dart @@ -192,6 +192,8 @@ class FilcColorPickerState extends State { @override Widget build(BuildContext context) { + bool advOverride = widget.colorMode == CustomColorMode.grade ? true : false; + if (MediaQuery.of(context).orientation == Orientation.portrait || widget.portraitOnly) { return Column( @@ -218,7 +220,7 @@ class FilcColorPickerState extends State { child: colorPickerSlider(TrackType.saturation), ), ), - if (isAdvancedView) + if (isAdvancedView || advOverride) Padding( padding: const EdgeInsets.only(left: 12.0, right: 12.0), child: SizedBox( @@ -230,7 +232,7 @@ class FilcColorPickerState extends State { ], ), ), - if (isAdvancedView && + if ((isAdvancedView || advOverride) && widget.colorMode != CustomColorMode.theme && widget.colorMode != CustomColorMode.enterId) Padding( @@ -295,7 +297,8 @@ class FilcColorPickerState extends State { ], ), ), - if (widget.colorMode != CustomColorMode.enterId) + if (widget.colorMode != CustomColorMode.enterId && + widget.colorMode != CustomColorMode.grade) SizedBox( height: 70 * (widget.colorMode == CustomColorMode.theme ? 2 : 1), child: BlockPicker( @@ -347,7 +350,8 @@ class FilcColorPickerState extends State { ), ), if (widget.colorMode != CustomColorMode.theme && - widget.colorMode != CustomColorMode.enterId) + widget.colorMode != CustomColorMode.enterId && + !advOverride) Material( color: Colors.transparent, child: InkWell( diff --git a/refilc/lib/ui/widgets/lesson/lesson_tile.dart b/refilc/lib/ui/widgets/lesson/lesson_tile.dart index f2041da..b7ab6f1 100644 --- a/refilc/lib/ui/widgets/lesson/lesson_tile.dart +++ b/refilc/lib/ui/widgets/lesson/lesson_tile.dart @@ -1,3 +1,4 @@ +import 'package:i18n_extension/i18n_extension.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc_kreta_api/providers/exam_provider.dart'; import 'package:refilc_kreta_api/providers/homework_provider.dart'; @@ -8,7 +9,8 @@ import 'package:refilc_kreta_api/models/lesson.dart'; import 'package:refilc/utils/format.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/round_border_icon.dart'; -import 'package:refilc_mobile_ui/common/widgets/exam/exam_view.dart'; +// import 'package:refilc_mobile_ui/common/widgets/exam/exam_view.dart'; +import 'package:refilc_mobile_ui/common/widgets/exam/exam_viewable.dart'; import 'package:refilc_mobile_ui/common/widgets/homework/homework_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; @@ -109,7 +111,8 @@ class LessonTile extends StatelessWidget { title: exam.description != "" ? exam.description : exam.mode?.description ?? "exam".i18n, - onPressed: () => ExamView.show(exam, context: context), + // onPressed: () => ExamView.show(exam, context: context), + onPressed: () => ExamPopup.show(context: context, exam: exam), )); } } @@ -190,7 +193,7 @@ class LessonTile extends StatelessWidget { : Transform.translate( offset: const Offset(0, -2.0), child: Text( - "${DateFormat("H:mm").format(lesson.start)}-${DateFormat("H:mm").format(lesson.end)}", + "${DateFormat("E, H:mm", I18n.of(context).locale.toString()).format(lesson.start)}-${DateFormat("H:mm").format(lesson.end)}", textAlign: TextAlign.start, style: TextStyle( fontWeight: FontWeight.w600, diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index 84da3a2..b2a4288 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak." homepage: https://refilc.hu publish_to: "none" -version: 5.0.0+257 +version: 5.0.1+265 environment: sdk: ">=2.17.0 <=3.3.2" @@ -104,6 +104,7 @@ flutter: - assets/svg/menu_icons/ - assets/other/dirtywords.xml - assets/svg/ + - assets/svg/cover_arts/ fonts: - family: FilcIcons diff --git a/refilc_desktop_ui/lib/pages/grades/grades_page.dart b/refilc_desktop_ui/lib/pages/grades/grades_page.dart index 3eedde6..24c9ec2 100644 --- a/refilc_desktop_ui/lib/pages/grades/grades_page.dart +++ b/refilc_desktop_ui/lib/pages/grades/grades_page.dart @@ -268,7 +268,7 @@ class GradesPageState extends State { title: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( - "Grades".i18n, + "page_title_grades".i18n, style: TextStyle( color: AppColors.of(context).text, fontSize: 32.0, diff --git a/refilc_kreta_api/lib/models/student.dart b/refilc_kreta_api/lib/models/student.dart index a364731..31aa35e 100644 --- a/refilc_kreta_api/lib/models/student.dart +++ b/refilc_kreta_api/lib/models/student.dart @@ -11,6 +11,7 @@ class Student { String? address; String? groupId; List parents; + // List parentsPhone; String? className; Student({ @@ -21,6 +22,7 @@ class Student { required this.yearId, this.address, required this.parents, + // required this.parentsPhone, this.json, }); diff --git a/refilc_kreta_api/lib/providers/grade_provider.dart b/refilc_kreta_api/lib/providers/grade_provider.dart index 3ab9701..f0e07ba 100644 --- a/refilc_kreta_api/lib/providers/grade_provider.dart +++ b/refilc_kreta_api/lib/providers/grade_provider.dart @@ -50,7 +50,8 @@ class GradeProvider with ChangeNotifier { String? userId = _user.id; if (userId != null) { final userStore = _database.userStore; - userStore.storeLastSeen(DateTime.now(), userId: userId, category: LastSeenCategory.surprisegrade); + userStore.storeLastSeen(DateTime.now(), + userId: userId, category: LastSeenCategory.surprisegrade); _lastSeen = DateTime.now(); } } @@ -59,7 +60,8 @@ class GradeProvider with ChangeNotifier { String? userId = _user.id; if (userId != null) { final userStore = _database.userStore; - userStore.storeLastSeen(DateTime(1969), userId: userId, category: LastSeenCategory.surprisegrade); + userStore.storeLastSeen(DateTime(1969), + userId: userId, category: LastSeenCategory.surprisegrade); _lastSeen = DateTime(1969); } } @@ -73,9 +75,11 @@ class GradeProvider with ChangeNotifier { _grades = await userQuery.getGrades(userId: userId); await convertBySettings(); + await getGradeStreak(); _groupAvg = await userQuery.getGroupAverages(userId: userId); notifyListeners(); - DateTime lastSeenDB = await userQuery.lastSeen(userId: userId, category: LastSeenCategory.surprisegrade); + DateTime lastSeenDB = await userQuery.lastSeen( + userId: userId, category: LastSeenCategory.surprisegrade); if (lastSeenDB.millisecondsSinceEpoch == 0 || lastSeenDB.year == 0 || !_settings.gradeOpeningFun) { @@ -133,6 +137,30 @@ class GradeProvider with ChangeNotifier { notifyListeners(); } + // get current grade streak and set it to the user + Future getGradeStreak() async { + User? user = _user.user; + if (user == null) throw "Cannot get Grade Streak for User null"; + + // streak magic + int gradeStreak = 0; + List grs = _grades; + grs.sort((a, b) => -a.date.compareTo(b.date)); + + for (Grade grade in grs) { + if (grade.value.value == 5) { + gradeStreak++; + } else { + break; + } + } + + print(gradeStreak); + + user.gradeStreak = gradeStreak; + notifyListeners(); + } + // Fetches Grades from the Kreta API then stores them in the database Future fetch() async { // test cucc @@ -173,6 +201,7 @@ class GradeProvider with ChangeNotifier { await _database.userStore.storeGrades(grades, userId: userId); _grades = grades; await convertBySettings(); + await getGradeStreak(); } Future storeGroupAvg(List groupAvgs) async { diff --git a/refilc_kreta_api/lib/providers/share_provider.dart b/refilc_kreta_api/lib/providers/share_provider.dart index e44d3b7..1df105c 100644 --- a/refilc_kreta_api/lib/providers/share_provider.dart +++ b/refilc_kreta_api/lib/providers/share_provider.dart @@ -19,11 +19,13 @@ class ShareProvider extends ChangeNotifier { // } // themes - Future shareCurrentTheme(BuildContext context, - {bool isPublic = false, - bool shareNick = true, - required SharedGradeColors gradeColors, - String displayName = ''}) async { + Future shareCurrentTheme( + BuildContext context, { + bool isPublic = false, + bool shareNick = true, + required SharedGradeColors gradeColors, + String displayName = '', + }) async { final SettingsProvider settings = Provider.of(context, listen: false); @@ -69,7 +71,25 @@ class ShareProvider extends ChangeNotifier { if (gradeColorsJson != null) { SharedTheme theme = SharedTheme.fromJson( - themeJson, SharedGradeColors.fromJson(gradeColorsJson)); + themeJson, + SharedGradeColors.fromJson(gradeColorsJson["public_id"] != '' + ? gradeColorsJson + : { + "public_id": "0", + "is_public": false, + "nickname": "Anonymous", + "five_color": + SettingsProvider.defaultSettings().gradeColors[4].value, + "four_color": + SettingsProvider.defaultSettings().gradeColors[3].value, + "three_color": + SettingsProvider.defaultSettings().gradeColors[2].value, + "two_color": + SettingsProvider.defaultSettings().gradeColors[1].value, + "one_color": + SettingsProvider.defaultSettings().gradeColors[0].value, + }), + ); return theme; } } @@ -93,7 +113,25 @@ class ShareProvider extends ChangeNotifier { if (gradeColorsJson != null) { SharedTheme theme = SharedTheme.fromJson( - t, SharedGradeColors.fromJson(gradeColorsJson)); + t, + SharedGradeColors.fromJson(gradeColorsJson["public_id"] != '' + ? gradeColorsJson + : { + "public_id": "0", + "is_public": false, + "nickname": "Anonymous", + "five_color": + SettingsProvider.defaultSettings().gradeColors[4].value, + "four_color": + SettingsProvider.defaultSettings().gradeColors[3].value, + "three_color": + SettingsProvider.defaultSettings().gradeColors[2].value, + "two_color": + SettingsProvider.defaultSettings().gradeColors[1].value, + "one_color": + SettingsProvider.defaultSettings().gradeColors[0].value, + }), + ); themes.add(theme); } @@ -134,8 +172,25 @@ class ShareProvider extends ChangeNotifier { Map? gradeColorsJson = await FilcAPI.getSharedGradeColors(id); if (gradeColorsJson != null) { - SharedGradeColors gradeColors = - SharedGradeColors.fromJson(gradeColorsJson); + SharedGradeColors gradeColors = SharedGradeColors.fromJson( + gradeColorsJson["public_id"] != '' + ? gradeColorsJson + : { + "public_id": "0", + "is_public": false, + "nickname": "Anonymous", + "five_color": + SettingsProvider.defaultSettings().gradeColors[4].value, + "four_color": + SettingsProvider.defaultSettings().gradeColors[3].value, + "three_color": + SettingsProvider.defaultSettings().gradeColors[2].value, + "two_color": + SettingsProvider.defaultSettings().gradeColors[1].value, + "one_color": + SettingsProvider.defaultSettings().gradeColors[0].value, + }, + ); return gradeColors; } diff --git a/refilc_mobile_ui/lib/common/average_display.dart b/refilc_mobile_ui/lib/common/average_display.dart index 3779e60..ebadd35 100644 --- a/refilc_mobile_ui/lib/common/average_display.dart +++ b/refilc_mobile_ui/lib/common/average_display.dart @@ -1,13 +1,22 @@ +import 'package:dotted_border/dotted_border.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/ui/widgets/grade/grade_tile.dart'; import 'package:flutter/material.dart'; import 'package:i18n_extension/i18n_extension.dart'; class AverageDisplay extends StatelessWidget { - const AverageDisplay({super.key, this.average = 0.0, this.border = false}); + const AverageDisplay({ + super.key, + this.average = 0.0, + this.border = false, + this.dashed = false, + this.scale = 1.0, + }); final double average; final bool border; + final bool dashed; + final double scale; @override Widget build(BuildContext context) { @@ -20,25 +29,44 @@ class AverageDisplay extends StatelessWidget { averageText = averageText.replaceAll(".", ","); } + Widget txtWidget = Text( + average == 0.0 ? "-" : averageText, + textAlign: TextAlign.center, + style: TextStyle( + color: color, fontWeight: FontWeight.w600, fontSize: scale * 15.0), + maxLines: 1, + ); + return Container( - width: border ? 57.0 : 54.0, - padding: EdgeInsets.symmetric( - horizontal: 6.0 - (border ? 2 : 0), vertical: 5.0 - (border ? 2 : 0)), + width: (border ? 57.0 : 54.0) * scale, + padding: (border && dashed) + ? null + : EdgeInsets.symmetric( + horizontal: (6.0 - (border ? 2 : 0)) * scale, + vertical: (5.0 - (border ? 2 : 0))) * + scale, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(45.0), - border: border + borderRadius: + (border && dashed) ? null : BorderRadius.circular(45.0 * scale), + border: border && !dashed ? Border.fromBorderSide( - BorderSide(color: color.withOpacity(.5), width: 1.0)) + BorderSide(color: color.withOpacity(.5), width: 1.0 * scale)) : null, color: !border ? color.withOpacity(average == 0.0 ? .15 : .25) : null, ), - child: Text( - average == 0.0 ? "-" : averageText, - textAlign: TextAlign.center, - style: TextStyle( - color: color, fontWeight: FontWeight.w600, fontSize: 15.0), - maxLines: 1, - ), + child: (border && dashed) + ? DottedBorder( + strokeWidth: 1.0 * scale, + padding: EdgeInsets.all(4.0 * scale), + color: color.withOpacity(.5), + dashPattern: const [6, 6], + radius: Radius.circular(45.0 * scale), + borderType: BorderType.RRect, + child: Center( + child: txtWidget, + ), + ) + : txtWidget, ); } } diff --git a/refilc_mobile_ui/lib/common/hero_scrollview.dart b/refilc_mobile_ui/lib/common/hero_scrollview.dart index 15334fe..c6f6a35 100644 --- a/refilc_mobile_ui/lib/common/hero_scrollview.dart +++ b/refilc_mobile_ui/lib/common/hero_scrollview.dart @@ -1,4 +1,5 @@ import 'package:flutter_svg/svg.dart'; +import 'package:refilc/helpers/subject.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:flutter/material.dart'; import 'package:refilc/theme/colors/utils.dart'; @@ -113,7 +114,9 @@ class HeroScrollViewState extends State { Padding( padding: const EdgeInsets.only(top: 26.0), child: SvgPicture.asset( - "assets/svg/mesh_bg.svg", + // "assets/svg/mesh_bg.svg", + SubjectBooklet.resolveVariant( + context: context, subjectName: widget.title), // ignore: deprecated_member_use color: ColorsUtils() .darken( diff --git a/refilc_mobile_ui/lib/common/personality_card/personality_card.i18n.dart b/refilc_mobile_ui/lib/common/personality_card/personality_card.i18n.dart index 9c2d2b1..0c5cb28 100644 --- a/refilc_mobile_ui/lib/common/personality_card/personality_card.i18n.dart +++ b/refilc_mobile_ui/lib/common/personality_card/personality_card.i18n.dart @@ -49,6 +49,8 @@ extension Localization on String { "d_npc": "You're such a non-player character, we couldn't give you a personality.", "s_npc": "In-game playtime (hours)", + // other + "year_index": "Lesson Number", }, "hu_hu": { // main @@ -96,6 +98,8 @@ extension Localization on String { "d_npc": "Egy akkora nagy non-player character vagy, hogy neked semmilyen személyiség nem jutott ezen kívül.", "s_npc": "In-game playtime (óra)", + // other + "year_index": "Éves óraszám", }, "de_de": { // main @@ -144,6 +148,8 @@ extension Localization on String { "d_npc": "Du bist einfach so sehr wie ein Computer, dass wir dir nicht einmal eine Persönlichkeit geben konnten.", "s_npc": "Spielzeit (Stunden)", + // other + "year_index": "Ordinalzahl", } }; diff --git a/refilc_mobile_ui/lib/common/profile_image/profile_button.dart b/refilc_mobile_ui/lib/common/profile_image/profile_button.dart index e63eba2..630ba0c 100644 --- a/refilc_mobile_ui/lib/common/profile_image/profile_button.dart +++ b/refilc_mobile_ui/lib/common/profile_image/profile_button.dart @@ -65,6 +65,7 @@ class ProfileButton extends StatelessWidget { radius: child.radius, badge: child.badge, role: child.role, + gradeStreak: child.gradeStreak, profilePictureString: child.profilePictureString, onTap: () { showSlidingBottomSheet( diff --git a/refilc_mobile_ui/lib/common/profile_image/profile_image.dart b/refilc_mobile_ui/lib/common/profile_image/profile_image.dart index 440702b..6024a7f 100644 --- a/refilc_mobile_ui/lib/common/profile_image/profile_image.dart +++ b/refilc_mobile_ui/lib/common/profile_image/profile_image.dart @@ -21,6 +21,7 @@ class ProfileImage extends StatefulWidget { this.censored = false, this.profilePictureString = "", this.isNotePfp = false, + this.gradeStreak = false, }); final void Function()? onTap; @@ -35,6 +36,7 @@ class ProfileImage extends StatefulWidget { final bool censored; final String profilePictureString; final bool isNotePfp; + final bool gradeStreak; @override State createState() => _ProfileImageState(); @@ -145,6 +147,20 @@ class _ProfileImageState extends State { color: roleColor, size: widget.radius / 1.3), ), ), + + // streak indicator + // if (widget.gradeStreak) + // SizedBox( + // height: widget.radius * 2, + // width: widget.radius * 2, + // child: Container( + // alignment: Alignment.topLeft, + // child: Text( + // '🔥', + // style: TextStyle(fontSize: widget.radius * 0.9), + // ), + // ), + // ), ], ); } @@ -238,6 +254,29 @@ class _ProfileImageState extends State { ), ), + // streak indicator + if (widget.gradeStreak) + Hero( + tag: "${widget.heroTag!}streak_indicator", + child: FittedBox( + fit: BoxFit.fitHeight, + child: SizedBox( + height: widget.radius * 2, + width: widget.radius * 2, + child: Transform.translate( + offset: Offset(-widget.radius / 4, -widget.radius / 4), + child: Container( + alignment: Alignment.topLeft, + child: Text( + '🔥', + style: TextStyle(fontSize: widget.radius * 0.8), + ), + ), + ), + ), + ), + ), + Material( color: Colors.transparent, clipBehavior: Clip.hardEdge, diff --git a/refilc_mobile_ui/lib/common/widgets/exam/exam_viewable.dart b/refilc_mobile_ui/lib/common/widgets/exam/exam_viewable.dart index c8b33be..b8b4c33 100644 --- a/refilc_mobile_ui/lib/common/widgets/exam/exam_viewable.dart +++ b/refilc_mobile_ui/lib/common/widgets/exam/exam_viewable.dart @@ -1,6 +1,7 @@ // ignore_for_file: use_build_context_synchronously import 'package:flutter_svg/svg.dart'; +import 'package:i18n_extension/i18n_extension.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:refilc/helpers/subject.dart'; @@ -13,8 +14,11 @@ import 'package:refilc_kreta_api/models/exam.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/round_border_icon.dart'; +import 'package:refilc_mobile_ui/common/viewable.dart'; +import 'package:refilc_mobile_ui/common/widgets/card_handle.dart'; import 'package:refilc_mobile_ui/common/widgets/exam/exam_tile.dart'; import 'package:flutter/material.dart'; +import 'package:refilc_mobile_ui/common/widgets/exam/exam_view.dart'; class ExamViewable extends StatelessWidget { const ExamViewable(this.exam, @@ -26,22 +30,25 @@ class ExamViewable extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => ExamPopup.show(context: context, exam: exam), - child: ExamTile( + if (Provider.of(context).newPopups) { + return GestureDetector( + onTap: () => ExamPopup.show(context: context, exam: exam), + child: ExamTile( + exam, + showSubject: showSubject, + padding: tilePadding, + ), + ); + } + + return Viewable( + tile: ExamTile( exam, showSubject: showSubject, padding: tilePadding, ), + view: CardHandle(child: ExamView(exam)), ); - // return Viewable( - // tile: ExamTile( - // exam, - // showSubject: showSubject, - // padding: tilePadding, - // ), - // view: CardHandle(child: ExamView(exam)), - // ); } } @@ -98,7 +105,9 @@ class ExamPopup extends StatelessWidget { Stack( children: [ SvgPicture.asset( - "assets/svg/mesh_bg.svg", + // "assets/svg/mesh_bg.svg", + SubjectBooklet.resolveVariant( + context: context, subject: exam.subject), // ignore: deprecated_member_use color: ColorsUtils() .fade(context, Theme.of(context).colorScheme.secondary, @@ -210,13 +219,19 @@ class ExamPopup extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - exam.description.capital(), - style: TextStyle( - color: - AppColors.of(context).text.withOpacity(0.9), - fontSize: 16.0, - fontWeight: FontWeight.w600, + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: Text( + exam.description.capital(), + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(0.9), + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, ), ), Text( @@ -288,7 +303,7 @@ class ExamPopup extends StatelessWidget { ], ), Text( - '${DateFormat('H:mm').format(lesson!.start)} - ${DateFormat('H:mm').format(lesson!.end)}', + '${DateFormat('MMM d, H:mm', I18n.locale.countryCode).format(lesson!.start).capital()} - ${DateFormat('H:mm').format(lesson!.end)}', style: TextStyle( color: AppColors.of(context).text.withOpacity(0.85), diff --git a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_view.i18n.dart b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_view.i18n.dart index 28752ba..57bdff9 100644 --- a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_view.i18n.dart +++ b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_view.i18n.dart @@ -12,6 +12,7 @@ extension Localization on String { "l_desc": "Description...", "done": "Done", "cancel": "Cancel", + "year_index": "Lesson Number", }, "hu_hu": { "Room": "Terem", @@ -22,6 +23,7 @@ extension Localization on String { "l_desc": "Leírás...", "done": "Kész", "cancel": "Mégse", + "year_index": "Éves óraszám", }, "de_de": { "Room": "Raum", @@ -32,6 +34,7 @@ extension Localization on String { "l_desc": "Beschreibung...", "done": "Erledigt", "cancel": "Abbrechen", + "year_index": "Ordinalzahl", } }; diff --git a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart index b32e6b6..cec6307 100644 --- a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart +++ b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart @@ -1,3 +1,4 @@ +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_svg/svg.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -8,11 +9,21 @@ import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/utils.dart'; import 'package:refilc/utils/format.dart'; +import 'package:refilc_kreta_api/models/exam.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; +import 'package:refilc_kreta_api/providers/exam_provider.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; +import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; import 'package:refilc_mobile_ui/common/round_border_icon.dart'; import 'package:refilc/ui/widgets/lesson/lesson_tile.dart'; import 'package:flutter/material.dart'; +import 'package:refilc_mobile_ui/common/viewable.dart'; +import 'package:refilc_mobile_ui/common/widgets/card_handle.dart'; +import 'package:refilc_mobile_ui/common/widgets/lesson/lesson_view.dart'; +import 'package:refilc_plus/models/premium_scopes.dart'; +import 'package:refilc_plus/providers/plus_provider.dart'; +import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; +import 'lesson_view.i18n.dart'; class LessonViewable extends StatefulWidget { const LessonViewable( @@ -59,174 +70,169 @@ class LessonViewableState extends State { if (lsn.subject.id == '' || tile.lesson.isEmpty) return tile; - return GestureDetector( - onTap: () => TimetableLessonPopup.show( - context: context, - lesson: lsn, - ), - child: LessonTile( - lsn, - swapDesc: widget.swapDesc, - showSubTiles: widget.showSubTiles, - // onTap: () => TimetableLessonPopup.show( - // context: context, - // lesson: lsn, - // ), - ), + // check if new popup needed + if (Provider.of(context).newPopups) { + return GestureDetector( + onTap: () => TimetableLessonPopup.show( + context: context, + lesson: lsn, + ), + child: tile, + ); + } + + return Viewable( + tile: tile, + view: CardHandle(child: LessonView(lsn)), + actions: [ + PanelButton( + background: true, + title: Text( + "edit_lesson".i18n, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.timetableNotes)) { + PlusLockedFeaturePopup.show( + context: context, feature: PremiumFeature.timetableNotes); + + return; + } + + showDialog( + context: context, + builder: (context) => StatefulBuilder(builder: (context, setS) { + return AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(14.0))), + title: Text("edit_lesson".i18n), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // description + TextField( + controller: _descTxt, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12.0), + hintText: 'l_desc'.i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + size: 18.0, + ), + onPressed: () { + setState(() { + _descTxt.text = ''; + }); + }, + ), + ), + ), + // const SizedBox( + // height: 14.0, + // ), + // // class + // TextField( + // controller: _descTxt, + // onEditingComplete: () async { + // // SharedTheme? theme = await shareProvider.getThemeById( + // // context, + // // id: _paintId.text.replaceAll(' ', ''), + // // ); + + // // if (theme != null) { + // // // set theme variable + // // newThemeByID = theme; + + // // _paintId.clear(); + // // } else { + // // ScaffoldMessenger.of(context).showSnackBar( + // // CustomSnackBar( + // // content: Text("theme_not_found".i18n, + // // style: const TextStyle(color: Colors.white)), + // // backgroundColor: AppColors.of(context).red, + // // context: context, + // // ), + // // ); + // // } + // }, + // decoration: InputDecoration( + // border: OutlineInputBorder( + // borderSide: const BorderSide( + // color: Colors.grey, width: 1.5), + // borderRadius: BorderRadius.circular(12.0), + // ), + // focusedBorder: OutlineInputBorder( + // borderSide: const BorderSide( + // color: Colors.grey, width: 1.5), + // borderRadius: BorderRadius.circular(12.0), + // ), + // contentPadding: + // const EdgeInsets.symmetric(horizontal: 12.0), + // hintText: 'l_desc'.i18n, + // suffixIcon: IconButton( + // icon: const Icon( + // FeatherIcons.x, + // color: Colors.grey, + // size: 18.0, + // ), + // onPressed: () { + // setState(() { + // _descTxt.text = ''; + // }); + // }, + // ), + // ), + // ), + ], + ), + actions: [ + TextButton( + child: Text( + "cancel".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + TextButton( + child: Text( + "done".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () async { + saveLesson(); + + Navigator.of(context).pop(); + setState(() {}); + }, + ), + ], + ); + }), + ); + }, + ), + ], ); - - // return Viewable( - // tile: tile, - // view: CardHandle(child: LessonView(lsn)), - // actions: [ - // PanelButton( - // background: true, - // title: Text( - // "edit_lesson".i18n, - // textAlign: TextAlign.center, - // maxLines: 2, - // overflow: TextOverflow.ellipsis, - // ), - // onPressed: () { - // Navigator.of(context, rootNavigator: true).pop(); - - // if (!Provider.of(context, listen: false) - // .hasScope(PremiumScopes.timetableNotes)) { - // PlusLockedFeaturePopup.show( - // context: context, feature: PremiumFeature.timetableNotes); - - // return; - // } - - // showDialog( - // context: context, - // builder: (context) => StatefulBuilder(builder: (context, setS) { - // return AlertDialog( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.all(Radius.circular(14.0))), - // title: Text("edit_lesson".i18n), - // content: Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // // description - // TextField( - // controller: _descTxt, - // decoration: InputDecoration( - // border: OutlineInputBorder( - // borderSide: const BorderSide( - // color: Colors.grey, width: 1.5), - // borderRadius: BorderRadius.circular(12.0), - // ), - // focusedBorder: OutlineInputBorder( - // borderSide: const BorderSide( - // color: Colors.grey, width: 1.5), - // borderRadius: BorderRadius.circular(12.0), - // ), - // contentPadding: - // const EdgeInsets.symmetric(horizontal: 12.0), - // hintText: 'l_desc'.i18n, - // suffixIcon: IconButton( - // icon: const Icon( - // FeatherIcons.x, - // color: Colors.grey, - // size: 18.0, - // ), - // onPressed: () { - // setState(() { - // _descTxt.text = ''; - // }); - // }, - // ), - // ), - // ), - // // const SizedBox( - // // height: 14.0, - // // ), - // // // class - // // TextField( - // // controller: _descTxt, - // // onEditingComplete: () async { - // // // SharedTheme? theme = await shareProvider.getThemeById( - // // // context, - // // // id: _paintId.text.replaceAll(' ', ''), - // // // ); - - // // // if (theme != null) { - // // // // set theme variable - // // // newThemeByID = theme; - - // // // _paintId.clear(); - // // // } else { - // // // ScaffoldMessenger.of(context).showSnackBar( - // // // CustomSnackBar( - // // // content: Text("theme_not_found".i18n, - // // // style: const TextStyle(color: Colors.white)), - // // // backgroundColor: AppColors.of(context).red, - // // // context: context, - // // // ), - // // // ); - // // // } - // // }, - // // decoration: InputDecoration( - // // border: OutlineInputBorder( - // // borderSide: const BorderSide( - // // color: Colors.grey, width: 1.5), - // // borderRadius: BorderRadius.circular(12.0), - // // ), - // // focusedBorder: OutlineInputBorder( - // // borderSide: const BorderSide( - // // color: Colors.grey, width: 1.5), - // // borderRadius: BorderRadius.circular(12.0), - // // ), - // // contentPadding: - // // const EdgeInsets.symmetric(horizontal: 12.0), - // // hintText: 'l_desc'.i18n, - // // suffixIcon: IconButton( - // // icon: const Icon( - // // FeatherIcons.x, - // // color: Colors.grey, - // // size: 18.0, - // // ), - // // onPressed: () { - // // setState(() { - // // _descTxt.text = ''; - // // }); - // // }, - // // ), - // // ), - // // ), - // ], - // ), - // actions: [ - // TextButton( - // child: Text( - // "cancel".i18n, - // style: const TextStyle(fontWeight: FontWeight.w500), - // ), - // onPressed: () { - // Navigator.of(context).maybePop(); - // }, - // ), - // TextButton( - // child: Text( - // "done".i18n, - // style: const TextStyle(fontWeight: FontWeight.w500), - // ), - // onPressed: () async { - // saveLesson(); - - // Navigator.of(context).pop(); - // setState(() {}); - // }, - // ), - // ], - // ); - // }), - // ); - // }, - // ), - // ], - // ); } void saveLesson() async { @@ -283,6 +289,18 @@ class TimetableLessonPopup extends StatelessWidget { @override Widget build(BuildContext context) { + Exam? lessonExam; + + if (lesson.exam != "") { + Exam exam = Provider.of(context, listen: false) + .exams + .firstWhere((t) => t.id == lesson.exam, + orElse: () => Exam.fromJson({})); + if (exam.id != "") { + lessonExam = exam; + } + } + return Container( decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, @@ -295,7 +313,9 @@ class TimetableLessonPopup extends StatelessWidget { Stack( children: [ SvgPicture.asset( - "assets/svg/mesh_bg.svg", + // "assets/svg/mesh_bg.svg", + SubjectBooklet.resolveVariant( + context: context, subject: lesson.subject), // ignore: deprecated_member_use color: ColorsUtils() .fade(context, Theme.of(context).colorScheme.secondary, @@ -319,13 +339,13 @@ class TimetableLessonPopup extends StatelessWidget { .withOpacity(0.1), Theme.of(context).scaffoldBackgroundColor, ], - stops: const [0.1, 0.5, 0.7, 1.0], + stops: const [0.0, 0.3, 0.6, 0.95], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), width: MediaQuery.of(context).size.width, - height: 175.0, + height: 200.0, ), ], ), @@ -340,16 +360,27 @@ class TimetableLessonPopup extends StatelessWidget { width: 40, height: 4, decoration: BoxDecoration( - color: ColorsUtils() - .fade( - context, Theme.of(context).colorScheme.secondary, - darkenAmount: 0.1, lightenAmount: 0.1) - .withOpacity(0.33), + color: Theme.of(context).scaffoldBackgroundColor, borderRadius: BorderRadius.circular( 2.0, ), ), + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: ColorsUtils() + .fade(context, + Theme.of(context).colorScheme.secondary, + darkenAmount: 0.1, lightenAmount: 0.1) + .withOpacity(0.33), + borderRadius: BorderRadius.circular( + 2.0, + ), + ), + ), ), + const SizedBox( height: 38.0, ), @@ -387,11 +418,9 @@ class TimetableLessonPopup extends StatelessWidget { width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.vertical( - top: const Radius.circular(12.0), - bottom: (lesson.description.replaceAll(' ', '') != '') - ? const Radius.circular(6.0) - : const Radius.circular(12.0), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(6.0), ), ), padding: const EdgeInsets.all(14.0), @@ -466,7 +495,15 @@ class TimetableLessonPopup extends StatelessWidget { height: 8.0, ), Text( - lesson.teacher.name, + ((lesson.substituteTeacher == null || + lesson.substituteTeacher!.name == "") + ? (lesson.teacher.isRenamed + ? lesson.teacher.renamedTo + : lesson.teacher.name) + : (lesson.substituteTeacher!.isRenamed + ? lesson.substituteTeacher!.renamedTo + : lesson.substituteTeacher!.name)) ?? + '', style: TextStyle( color: AppColors.of(context).text.withOpacity(0.9), fontSize: 14.0, @@ -487,7 +524,7 @@ class TimetableLessonPopup extends StatelessWidget { color: Theme.of(context).colorScheme.background, borderRadius: const BorderRadius.vertical( top: Radius.circular(6.0), - bottom: Radius.circular(12.0), + bottom: Radius.circular(6.0), ), ), padding: const EdgeInsets.all(14.0), @@ -506,6 +543,94 @@ class TimetableLessonPopup extends StatelessWidget { ], ), ), + const SizedBox( + height: 6.0, + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.vertical( + top: const Radius.circular(6.0), + bottom: lesson.exam != '' + ? const Radius.circular(6.0) + : const Radius.circular(12.0), + ), + ), + padding: const EdgeInsets.all(14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${'year_index'.i18n}: ${lesson.lessonYearIndex ?? '?'}', + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.9), + fontSize: 14.0, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + if (lesson.exam != '') + const SizedBox( + height: 6.0, + ), + if (lesson.exam != '' && lessonExam != null) + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(6.0), + bottom: Radius.circular(12.0)), + ), + padding: const EdgeInsets.all(14.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Icon( + FeatherIcons.file, + size: 20.0, + ), + const SizedBox( + width: 10.0, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.5, + child: Text( + lessonExam.description.capital(), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(0.9), + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + Flexible( + child: Text( + lessonExam.mode?.description ?? 'Dolgozat', + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(0.85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), // const SizedBox( // height: 24.0, // ), diff --git a/refilc_mobile_ui/lib/pages/absences/absences_page.dart b/refilc_mobile_ui/lib/pages/absences/absences_page.dart index ff339db..df77029 100644 --- a/refilc_mobile_ui/lib/pages/absences/absences_page.dart +++ b/refilc_mobile_ui/lib/pages/absences/absences_page.dart @@ -4,7 +4,9 @@ import 'dart:math'; import 'package:animations/animations.dart'; import 'package:collection/collection.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:refilc/api/providers/update_provider.dart'; +import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/colors/utils.dart'; import 'package:refilc/ui/date_widget.dart'; import 'package:refilc_kreta_api/models/absence.dart'; @@ -169,6 +171,7 @@ class AbsencesPageState extends State badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -179,10 +182,22 @@ class AbsencesPageState extends State padding: const EdgeInsets.only(left: 8.0), child: Text( "Absences".i18n, - style: TextStyle( - color: AppColors.of(context).text, - fontSize: 32.0, - fontWeight: FontWeight.bold), + style: Provider.of(context).fontFamily != + '' && + Provider.of(context).titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context).fontFamily, + textStyle: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold, + ), + ) + : TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold, + ), ), ), bottom: FilterBar( @@ -504,7 +519,9 @@ class AbsencesPageState extends State style: TextStyle( fontSize: 18.0, fontWeight: FontWeight.w700, - color: AppColors.of(context).green, + color: value1 > 0 + ? AppColors.of(context).green + : AppColors.of(context).text, ), ), Text( @@ -513,12 +530,14 @@ class AbsencesPageState extends State fontSize: 16.0, fontWeight: FontWeight.w500, height: 1.1, - color: ColorsUtils().fade( - context, - AppColors.of(context).green, - darkenAmount: 0.5, - lightenAmount: 0.4, - ), + color: value1 > 0 + ? ColorsUtils().fade( + context, + AppColors.of(context).green, + darkenAmount: 0.5, + lightenAmount: 0.4, + ) + : AppColors.of(context).text, ), ), ], @@ -535,7 +554,9 @@ class AbsencesPageState extends State style: TextStyle( fontSize: 18.0, fontWeight: FontWeight.w700, - color: AppColors.of(context).red, + color: value2 > 0 + ? AppColors.of(context).red + : AppColors.of(context).text, ), ), Text( @@ -544,12 +565,14 @@ class AbsencesPageState extends State fontSize: 16.0, fontWeight: FontWeight.w500, height: 1.1, - color: ColorsUtils().fade( - context, - AppColors.of(context).red, - darkenAmount: 0.4, - lightenAmount: 0.2, - ), + color: value2 > 0 + ? ColorsUtils().fade( + context, + AppColors.of(context).red, + darkenAmount: 0.4, + lightenAmount: 0.2, + ) + : AppColors.of(context).text, ), ), ], @@ -597,8 +620,9 @@ class AbsencesPageState extends State children: [ Row( children: [ - const Icon( + Icon( Icons.av_timer_rounded, + color: value3 > 0 ? Colors.orange : null, ), const SizedBox( width: 10.0, diff --git a/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart b/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart index bc321e0..7583707 100644 --- a/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart +++ b/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart @@ -34,7 +34,6 @@ import 'package:refilc_mobile_ui/pages/grades/calculator/grade_calculator_provid import 'package:refilc_mobile_ui/pages/grades/grades_count.dart'; import 'package:refilc_mobile_ui/pages/grades/graph.dart'; import 'package:refilc_mobile_ui/pages/grades/subject_grades_container.dart'; -import 'package:refilc_plus/ui/mobile/goal_planner/goal_planner_screen.dart'; // import 'package:refilc_plus/models/premium_scopes.dart'; // import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/goal_planner/goal_state_screen.dart'; @@ -44,6 +43,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; +import 'package:refilc_plus/ui/mobile/goal_planner/goal_track_popup.dart'; import 'grades_page.i18n.dart'; // import 'package:refilc_plus/ui/mobile/goal_planner/new_goal.dart'; @@ -425,9 +425,10 @@ class _GradeSubjectViewState extends State // ScaffoldMessenger.of(context).showSnackBar( // const SnackBar(content: Text("Hamarosan..."))); - Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => - GoalPlannerScreen(subject: widget.subject))); + // Navigator.of(context).push(CupertinoPageRoute( + // builder: (context) => + // GoalPlannerScreen(subject: widget.subject))); + GoalTrackPopup.show(context, subject: widget.subject); }, child: const Icon(FeatherIcons.flag, size: 20.0), ), diff --git a/refilc_mobile_ui/lib/pages/grades/grades_page.dart b/refilc_mobile_ui/lib/pages/grades/grades_page.dart index 6f8d084..4f33264 100644 --- a/refilc_mobile_ui/lib/pages/grades/grades_page.dart +++ b/refilc_mobile_ui/lib/pages/grades/grades_page.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:collection/collection.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:refilc/api/providers/update_provider.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc/ui/widgets/grade/grade_tile.dart'; @@ -267,6 +268,12 @@ class GradesPageState extends State { ), ), ), + if (hasHomework && + nearestExam != null && + Provider.of(context).qSubjectsSubTiles) + const SizedBox( + height: 6.0, + ), if (nearestExam != null && Provider.of(context).qSubjectsSubTiles) Container( @@ -560,6 +567,7 @@ class GradesPageState extends State { badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -568,11 +576,21 @@ class GradesPageState extends State { title: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( - "Grades".i18n, - style: TextStyle( - color: AppColors.of(context).text, - fontSize: 32.0, - fontWeight: FontWeight.bold), + "page_title_grades".i18n, + style: Provider.of(context).fontFamily != + '' && + Provider.of(context).titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context).fontFamily, + textStyle: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), + ) + : TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), ), ), shadowColor: Theme.of(context).shadowColor, @@ -667,6 +685,8 @@ class GradesPageState extends State { // SoonAlert.show(context: context); gradeCalcTotal(context); + + Navigator.of(context, rootNavigator: true).pop(); }, ), ), diff --git a/refilc_mobile_ui/lib/pages/grades/grades_page.i18n.dart b/refilc_mobile_ui/lib/pages/grades/grades_page.i18n.dart index 59eee56..e4092ee 100644 --- a/refilc_mobile_ui/lib/pages/grades/grades_page.i18n.dart +++ b/refilc_mobile_ui/lib/pages/grades/grades_page.i18n.dart @@ -31,7 +31,8 @@ extension Localization on String { "grade_calc": "Grade Calculator", }, "hu_hu": { - "Grades": "Tantárgyak", + "Grades": "Jegyek", + "page_title_grades": "Tantárgyak", "Ghost Grades": "Szellem jegyek", "Subjects": "Tantárgyaid", "Subjects_changes": "Tantárgyi változások", diff --git a/refilc_mobile_ui/lib/pages/grades/graph.dart b/refilc_mobile_ui/lib/pages/grades/graph.dart index ed8e2e8..a52720b 100644 --- a/refilc_mobile_ui/lib/pages/grades/graph.dart +++ b/refilc_mobile_ui/lib/pages/grades/graph.dart @@ -248,11 +248,12 @@ class GradeGraphState extends State { ), if (ghostData.isNotEmpty && ghostSpots.isNotEmpty) LineChartBarData( - preventCurveOverShooting: true, + preventCurveOverShooting: false, spots: ghostSpots, isCurved: true, colors: [AppColors.of(context).text], - barWidth: 8, + barWidth: 6, + curveSmoothness: 0.2, isStrokeCapRound: true, dotData: FlDotData(show: false), belowBarData: BarAreaData( diff --git a/refilc_mobile_ui/lib/pages/home/home_page.dart b/refilc_mobile_ui/lib/pages/home/home_page.dart index 8a7218e..8ccd9c6 100644 --- a/refilc_mobile_ui/lib/pages/home/home_page.dart +++ b/refilc_mobile_ui/lib/pages/home/home_page.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:refilc/api/providers/live_card_provider.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/ui/date_widget.dart'; @@ -244,14 +245,34 @@ class HomePageState extends State with TickerProviderStateMixin { greeting, overflow: TextOverflow.fade, textAlign: TextAlign.start, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18.0, - color: Theme.of(context) - .textTheme - .bodyMedium - ?.color, - ), + style: + Provider.of(context) + .fontFamily != + '' && + Provider.of( + context) + .titleOnlyFont + ? GoogleFonts.getFont( + Provider.of( + context) + .fontFamily, + textStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, + ), + ) + : TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, + ), ), Text( DateFormat('EEEE, MMM d', @@ -317,6 +338,7 @@ class HomePageState extends State with TickerProviderStateMixin { badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -355,7 +377,7 @@ class HomePageState extends State with TickerProviderStateMixin { LiveCardState.duringLesson || _liveCard.currentState == LiveCardState.duringBreak) - ? 55.0 + ? 62.0 : 52.0), ), child: Transform.scale( diff --git a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart index 922070a..1092e9d 100644 --- a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart +++ b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart @@ -404,7 +404,7 @@ class LiveCardStateA extends State { swapRoom: true, currentLessonIndicator: false, padding: - const EdgeInsets.only(top: 6.0, bottom: 4.0), + const EdgeInsets.only(top: 2.0, bottom: 4.0), contentPadding: EdgeInsets.zero, showSubTiles: false, ), @@ -911,6 +911,37 @@ class LiveCardStateA extends State { Row( children: liveCard.nextLesson != null ? [ + Container( + width: (liveCard.nextLesson?.room + .length ?? + 0) > + 20 + ? 111 + : null, + padding: const EdgeInsets.symmetric( + horizontal: 5.5, vertical: 3.0), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .tertiary + .withOpacity(.15), + borderRadius: + BorderRadius.circular(10.0), + ), + child: Text( + liveCard.nextLesson!.room, + overflow: TextOverflow.ellipsis, + style: TextStyle( + height: 1.1, + fontSize: 12.0, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .colorScheme + .secondary + .withOpacity(.9), + ), + ), + ), const SizedBox( width: 10, ), diff --git a/refilc_mobile_ui/lib/pages/messages/messages_page.dart b/refilc_mobile_ui/lib/pages/messages/messages_page.dart index b36ea1c..117c280 100644 --- a/refilc_mobile_ui/lib/pages/messages/messages_page.dart +++ b/refilc_mobile_ui/lib/pages/messages/messages_page.dart @@ -1,6 +1,8 @@ import 'dart:math'; +import 'package:google_fonts/google_fonts.dart'; import 'package:refilc/api/providers/update_provider.dart'; +import 'package:refilc/models/settings.dart'; import 'package:refilc/ui/date_widget.dart'; import 'package:refilc_kreta_api/providers/message_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; @@ -109,6 +111,7 @@ class MessagesPageState extends State badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -128,10 +131,22 @@ class MessagesPageState extends State ), Text( "Messages".i18n, - style: TextStyle( - color: AppColors.of(context).text, - fontSize: 32.0, - fontWeight: FontWeight.bold), + style: Provider.of(context) + .fontFamily != + '' && + Provider.of(context) + .titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context).fontFamily, + textStyle: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), + ) + : TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), ), ], ), diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.dart index b9550b0..5921b59 100644 --- a/refilc_mobile_ui/lib/pages/notes/notes_page.dart +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.dart @@ -5,6 +5,7 @@ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/self_note_provider.dart'; import 'package:refilc/api/providers/update_provider.dart'; @@ -35,6 +36,7 @@ import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/premium_inline.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; +import 'package:uuid/uuid.dart'; import 'notes_page.i18n.dart'; enum AbsenceFilter { absences, delays, misses } @@ -65,9 +67,14 @@ class NotesPageState extends State with TickerProviderStateMixin { Map doneItems = {}; List noteTiles = []; + List todoItems = []; + + final TextEditingController _taskName = TextEditingController(); + final TextEditingController _taskContent = TextEditingController(); void generateTiles() async { doneItems = await databaseProvider.userQuery.toDoItems(userId: user.id!); + todoItems = await databaseProvider.userQuery.getTodoItems(userId: user.id!); List tiles = []; @@ -82,7 +89,7 @@ class NotesPageState extends State with TickerProviderStateMixin { List toDoTiles = []; if (hw.isNotEmpty && - !Provider.of(context, listen: false) + Provider.of(context, listen: false) .hasScope(PremiumScopes.unlimitedSelfNotes)) { toDoTiles.addAll(hw.map((e) => TickTile( padding: EdgeInsets.zero, @@ -102,6 +109,21 @@ class NotesPageState extends State with TickerProviderStateMixin { ))); } + if (selfNoteProvider.todos.isNotEmpty) { + toDoTiles.addAll(selfNoteProvider.todos.map((e) => TickTile( + padding: EdgeInsets.zero, + title: e.title, + description: e.content, + isTicked: e.done, + onTap: (p0) async { + todoItems.firstWhere((element) => element.id == e.id).done = p0; + + await databaseProvider.userStore + .storeSelfTodoItems(todoItems, userId: user.id!); + }, + ))); + } + if (toDoTiles.isNotEmpty) { tiles.add(const SizedBox( height: 10.0, @@ -128,26 +150,31 @@ class NotesPageState extends State with TickerProviderStateMixin { CupertinoPageRoute( builder: (context) => NoteViewScreen(note: e))), ) - : Container( - height: MediaQuery.of(context).size.width / 2.42, - width: MediaQuery.of(context).size.width / 2.42, - decoration: BoxDecoration( - boxShadow: [ - if (Provider.of(context, listen: false) - .shadowEffect) - BoxShadow( - offset: const Offset(0, 21), - blurRadius: 23.0, - color: Theme.of(context).shadowColor, - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16.0), - child: Image.memory( - const Base64Decoder().convert(e.content), - fit: BoxFit.cover, - gaplessPlayback: true, + : GestureDetector( + onTap: () => Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => NoteViewScreen(note: e))), + child: Container( + height: MediaQuery.of(context).size.width / 2.42, + width: MediaQuery.of(context).size.width / 2.42, + decoration: BoxDecoration( + boxShadow: [ + if (Provider.of(context, listen: false) + .shadowEffect) + BoxShadow( + offset: const Offset(0, 21), + blurRadius: 23.0, + color: Theme.of(context).shadowColor, + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16.0), + child: Image.memory( + const Base64Decoder().convert(e.content), + fit: BoxFit.cover, + gaplessPlayback: true, + ), ), ), ), @@ -289,6 +316,7 @@ class NotesPageState extends State with TickerProviderStateMixin { badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -299,10 +327,20 @@ class NotesPageState extends State with TickerProviderStateMixin { padding: const EdgeInsets.only(left: 8.0), child: Text( "notes".i18n, - style: TextStyle( - color: AppColors.of(context).text, - fontSize: 32.0, - fontWeight: FontWeight.bold), + style: Provider.of(context).fontFamily != + '' && + Provider.of(context).titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context).fontFamily, + textStyle: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), + ) + : TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), ), ), ), @@ -313,6 +351,8 @@ class NotesPageState extends State with TickerProviderStateMixin { .fetch( from: DateTime.now().subtract(const Duration(days: 30))); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false) + .restoreTodo(); generateTiles(); @@ -410,7 +450,159 @@ class NotesPageState extends State with TickerProviderStateMixin { }, ), ), + const SizedBox( + height: 10.0, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + color: Theme.of(context).colorScheme.background), + child: ListTile( + title: Row( + children: [ + const Icon(Icons.task_outlined), + const SizedBox( + width: 10.0, + ), + Text('new_task'.i18n), + ], + ), + onTap: () { + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.unlimitedSelfNotes)) { + PlusLockedFeaturePopup.show( + context: context, feature: PremiumFeature.selfNotes); + + return; + } + + showTaskCreation(context); + }, + ), + ), ]), ); } + + void showTaskCreation(context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(14.0))), + contentPadding: const EdgeInsets.only(top: 10.0), + title: Text("new_task".i18n), + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _taskName, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: "task_name".i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _taskName.text = ""; + }); + }, + ), + ), + ), + const SizedBox( + height: 10.0, + ), + TextField( + controller: _taskContent, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: "task_content".i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _taskContent.text = ""; + }); + }, + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + child: Text( + "cancel".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + TextButton( + child: Text( + "next".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () async { + todoItems.add(TodoItem.fromJson({ + 'id': const Uuid().v4(), + 'title': _taskName.text.replaceAll(' ', '') != "" + ? _taskName.text + : 'no_title'.i18n, + 'content': _taskContent.text, + 'done': false, + })); + + await databaseProvider.userStore + .storeSelfTodoItems(todoItems, userId: user.id!); + + setState(() { + _taskName.text = ""; + _taskContent.text = ""; + }); + + Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false) + .restoreTodo(); + + generateTiles(); + + Navigator.of(context).pop(true); + }, + ), + ], + ), + ); + } } diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart index f452a9e..28bc703 100644 --- a/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart @@ -14,6 +14,10 @@ extension ScreensLocalization on String { "hint_t": "Note title...", "your_notes": "Your Notes", "new_image": "New Image", + "no_title": "No title", + "task_content": "Task content...", + "task_name": "Task title...", + "new_task": "New Task", }, "hu_hu": { "notes": "Füzet", @@ -26,6 +30,10 @@ extension ScreensLocalization on String { "hint_t": "Jegyzet címe...", "your_notes": "Jegyzeteid", "new_image": "Új kép", + "no_title": "Nincs cím", + "task_content": "Feladat tartalma...", + "task_name": "Feladat címe...", + "new_task": "Új feladat", }, "de_de": { "notes": "Broschüre", @@ -38,6 +46,10 @@ extension ScreensLocalization on String { "hint_t": "Titel notieren...", "your_notes": "Deine Noten", "new_image": "Neues Bild", + "no_title": "Kein Titel", + "task_content": "Aufgabeninhalt...", + "task_name": "Aufgabentitel...", + "new_task": "Neue Aufgabe", }, }; diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/add_note_screen.dart b/refilc_mobile_ui/lib/pages/notes/submenu/add_note_screen.dart index 30c9bb3..f4e3622 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/add_note_screen.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/add_note_screen.dart @@ -257,6 +257,8 @@ class AddNoteScreenState extends State { style: const TextStyle(fontSize: 16.0), ), ), + if (MediaQuery.of(context).viewInsets.bottom != 0) + const SizedBox(height: 60), ], ), ), diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart b/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart index c81faf7..765338b 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:image_crop/image_crop.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; @@ -27,6 +28,8 @@ class ImageNoteEditor extends StatefulWidget { } class _ImageNoteEditorState extends State { + final _title = TextEditingController(); + final cropKey = GlobalKey(); File? _file; File? _sample; @@ -62,7 +65,7 @@ class _ImageNoteEditorState extends State { child: Crop.file( _sample!, key: cropKey, - aspectRatio: 1.0, + // aspectRatio: 1.0, ), ); } @@ -135,7 +138,8 @@ class _ImageNoteEditorState extends State { selfNotes.add(SelfNote.fromJson({ 'id': const Uuid().v4(), 'content': base64Image, - 'note_type': 'image' + 'note_type': 'image', + 'title': _title.text, })); await Provider.of(context, listen: false) @@ -143,6 +147,7 @@ class _ImageNoteEditorState extends State { .storeSelfNotes(selfNotes, userId: widget.u.id); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false).restoreTodo(); debugPrint('$file'); } @@ -170,6 +175,37 @@ class _ImageNoteEditorState extends State { const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), child: _sample == null ? openImageWidget() : cropImageWidget(), ), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), + child: TextField( + controller: _title, + onEditingComplete: () async {}, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: 'title'.i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _title.text = ''; + }); + }, + ), + ), + ), + ), // if (widget.u.picture != "") // TextButton( // child: Text( diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/note_view_screen.dart b/refilc_mobile_ui/lib/pages/notes/submenu/note_view_screen.dart index 5ea5ddd..6c53209 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/note_view_screen.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/note_view_screen.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:refilc/api/providers/self_note_provider.dart'; import 'package:refilc/models/self_note.dart'; @@ -8,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; import 'package:markdown/markdown.dart' as md; +import 'notes_screen.i18n.dart'; class NoteViewScreen extends StatefulWidget { const NoteViewScreen({super.key, required this.note}); @@ -30,7 +33,9 @@ class NoteViewScreenState extends State { surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, leading: BackButton(color: AppColors.of(context).text), title: Text( - widget.note.title ?? '${widget.note.content.split(' ')[0]}...', + widget.note.noteType == NoteType.text + ? (widget.note.title ?? '${widget.note.content.split(' ')[0]}...') + : 'image_note'.i18n, style: TextStyle( color: AppColors.of(context).text, fontSize: 26.0, @@ -38,52 +43,55 @@ class NoteViewScreenState extends State { ), ), actions: [ - ClipRRect( - borderRadius: BorderRadius.circular(10.1), - child: GestureDetector( - onTap: () { - // handle tap - Navigator.of(context, rootNavigator: true).push( - CupertinoPageRoute( - builder: (context) => - AddNoteScreen(initialNote: widget.note))); - }, - child: Container( - color: Theme.of(context).colorScheme.secondary.withOpacity(0.2), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - IconTheme( - data: IconThemeData( - color: Theme.of(context).colorScheme.secondary, + if (widget.note.noteType == NoteType.text) + ClipRRect( + borderRadius: BorderRadius.circular(10.1), + child: GestureDetector( + onTap: () { + // handle tap + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => + AddNoteScreen(initialNote: widget.note))); + }, + child: Container( + color: + Theme.of(context).colorScheme.secondary.withOpacity(0.2), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Stack( + children: [ + IconTheme( + data: IconThemeData( + color: Theme.of(context).colorScheme.secondary, + ), + child: const Icon( + FeatherIcons.edit, + size: 20.0, + ), ), - child: const Icon( - FeatherIcons.edit, - size: 20.0, + IconTheme( + data: IconThemeData( + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withOpacity(.5) + : Colors.white.withOpacity(.3), + ), + child: const Icon( + FeatherIcons.edit, + size: 20.0, + ), ), - ), - IconTheme( - data: IconThemeData( - color: - Theme.of(context).brightness == Brightness.light - ? Colors.black.withOpacity(.5) - : Colors.white.withOpacity(.3), - ), - child: const Icon( - FeatherIcons.edit, - size: 20.0, - ), - ), - ], + ], + ), ), ), ), ), - ), - const SizedBox( - width: 10, - ), + if (widget.note.noteType == NoteType.text) + const SizedBox( + width: 10, + ), ClipRRect( borderRadius: BorderRadius.circular(10.1), child: GestureDetector( @@ -140,21 +148,30 @@ class NoteViewScreenState extends State { child: Column( children: [ Expanded( - child: MarkdownBody( - data: widget.note.content, - extensionSet: md.ExtensionSet( - md.ExtensionSet.gitHubFlavored.blockSyntaxes, - [ - md.EmojiSyntax(), - ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes - ], - ), - styleSheet: MarkdownStyleSheet( - p: const TextStyle( - fontSize: 15.0, - ), - ), - ), + child: widget.note.noteType == NoteType.text + ? MarkdownBody( + data: widget.note.content, + extensionSet: md.ExtensionSet( + md.ExtensionSet.gitHubFlavored.blockSyntaxes, + [ + md.EmojiSyntax(), + ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes + ], + ), + styleSheet: MarkdownStyleSheet( + p: const TextStyle( + fontSize: 15.0, + ), + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Image.memory( + const Base64Decoder().convert(widget.note.content), + fit: BoxFit.contain, + gaplessPlayback: true, + ), + ), ), // Expanded( // child: Text( diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart index 8f2795a..df9b01b 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart @@ -268,6 +268,7 @@ class NotesScreenState extends State { Provider.of(context, listen: false) .fetch(from: DateTime.now().subtract(const Duration(days: 30))); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false).restoreTodo(); return Future(() => null); }, diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.i18n.dart b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.i18n.dart index bbe6033..f8e3ed2 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.i18n.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.i18n.dart @@ -18,6 +18,8 @@ extension SettingsLocalization on String { "click_here": "Click here", "select_image": "to select an image", "new_image": "New Image", + "image_note": "Image", + "title": "Image title...", }, "hu_hu": { "notes": "Füzet", @@ -34,6 +36,8 @@ extension SettingsLocalization on String { "click_here": "Kattints ide", "select_image": "kép kiválasztásához", "new_image": "Új kép", + "image_note": "Kép", + "title": "Kép címe...", }, "de_de": { "notes": "Broschüre", @@ -50,6 +54,8 @@ extension SettingsLocalization on String { "click_here": "Klicken Sie hier", "select_image": "um ein Bild auszuwählen", "new_image": "Neues Bild", + "image_note": "Bild", + "title": "Bildtitel...", }, }; diff --git a/refilc_mobile_ui/lib/pages/timetable/day_title.dart b/refilc_mobile_ui/lib/pages/timetable/day_title.dart index c64317d..f6dedb5 100644 --- a/refilc_mobile_ui/lib/pages/timetable/day_title.dart +++ b/refilc_mobile_ui/lib/pages/timetable/day_title.dart @@ -1,3 +1,6 @@ +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:flutter/material.dart'; import 'package:refilc/utils/format.dart'; @@ -50,11 +53,26 @@ class _DayTitleState extends State { width: MediaQuery.of(context).size.width / 1.5, child: Text( widget.dayTitle(index).capital(), - style: TextStyle( - color: - AppColors.of(context).text.withOpacity(opacity), - fontSize: 32.0, - fontWeight: FontWeight.bold), + style: Provider.of(context) + .fontFamily != + '' && + Provider.of(context) + .titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context).fontFamily, + textStyle: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(opacity), + fontSize: 32.0, + fontWeight: FontWeight.bold), + ) + : TextStyle( + color: AppColors.of(context) + .text + .withOpacity(opacity), + fontSize: 32.0, + fontWeight: FontWeight.bold), ), ); }, diff --git a/refilc_mobile_ui/lib/pages/timetable/timetable_page.dart b/refilc_mobile_ui/lib/pages/timetable/timetable_page.dart index 03b983a..0610d60 100644 --- a/refilc_mobile_ui/lib/pages/timetable/timetable_page.dart +++ b/refilc_mobile_ui/lib/pages/timetable/timetable_page.dart @@ -1,5 +1,6 @@ import 'dart:math'; import 'package:animations/animations.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:i18n_extension/i18n_extension.dart'; import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/update_provider.dart'; @@ -19,7 +20,7 @@ import 'package:refilc_mobile_ui/common/empty.dart'; import 'package:refilc_mobile_ui/common/profile_image/profile_button.dart'; import 'package:refilc_mobile_ui/common/profile_image/profile_image.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart'; -import 'package:refilc_mobile_ui/common/widgets/lesson/lesson_view.dart'; +// import 'package:refilc_mobile_ui/common/widgets/lesson/lesson_view.dart'; import 'package:refilc_kreta_api/controllers/timetable_controller.dart'; import 'package:refilc_mobile_ui/common/widgets/lesson/lesson_viewable.dart'; import 'package:refilc_mobile_ui/pages/timetable/day_title.dart'; @@ -60,7 +61,11 @@ class TimetablePage extends StatefulWidget { NavigationScreen.of(context)?.setPage("timetable"); // Show initial Lesson - if (lesson != null) LessonView.show(lesson, context: context); + // if (lesson != null) LessonView.show(lesson, context: context); + // changed to new popup + if (lesson != null) { + TimetableLessonPopup.show(context: context, lesson: lesson); + } } @override @@ -316,6 +321,7 @@ class TimetablePageState extends State badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, ), ), ), @@ -374,11 +380,25 @@ class TimetablePageState extends State } else { return Text( "timetable".i18n, - style: TextStyle( - fontSize: 32.0, - fontWeight: FontWeight.bold, - color: AppColors.of(context).text, - ), + style: Provider.of(context) + .fontFamily != + '' && + Provider.of(context) + .titleOnlyFont + ? GoogleFonts.getFont( + Provider.of(context) + .fontFamily, + textStyle: TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.bold, + color: AppColors.of(context).text, + ), + ) + : TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.bold, + color: AppColors.of(context).text, + ), ); } }(), diff --git a/refilc_mobile_ui/lib/plus/components/plan_card.dart b/refilc_mobile_ui/lib/plus/components/plan_card.dart index d1a9567..18985b7 100644 --- a/refilc_mobile_ui/lib/plus/components/plan_card.dart +++ b/refilc_mobile_ui/lib/plus/components/plan_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:refilc/models/settings.dart'; import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; @@ -50,6 +51,20 @@ class PlusPlanCard extends StatelessWidget { return; } + if (Provider.of(context, listen: false).xFilcId == + "none") { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + "Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!", + style: + TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.white, + )); + + return; + } + if (Provider.of(context, listen: false).hasPremium) { if (!active) { launchUrl( diff --git a/refilc_mobile_ui/lib/plus/plus_screen.dart b/refilc_mobile_ui/lib/plus/plus_screen.dart index 385f20d..9cd7e8a 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.dart @@ -225,6 +225,7 @@ class PlusScreenState extends State { ['🎓', 'rfp_6'.i18n], ['👕', 'rfp_14'.i18n], ['👑', 'rfp_15'.i18n], + ['📩', 'rfp_17'.i18n], ['🔜', 'more_soon'.i18n], ], docsAccepted: docsAccepted, diff --git a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart index f10ea85..4bcfd28 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart @@ -45,6 +45,7 @@ extension SettingsLocalization on String { "rfp_14": "Discount in reFilc Shop (soon)", "rfp_15": "Subscriber role in our Discord community", "rfp_16": "Private leaks and informations about upcoming features", + "rfp_17": "Grade exporting", // other "and": " and ", "every": "Every ", @@ -96,6 +97,7 @@ extension SettingsLocalization on String { "rfp_14": "Kedvezmény a reFilc Shop-ban (hamarosan)", "rfp_15": "Előfizetői rang a Discord szerverünkön", "rfp_16": "Privát betekintések és információk közelgő újításokról", + "rfp_17": "Jegy exportálás", // other "and": " és ", "every": "Minden ", @@ -133,30 +135,32 @@ extension SettingsLocalization on String { "Der Preis wird in Euro angegeben im Bezug zum aktuellen Wechselkurs. 1 EUR ≈ 390 HUF", "active": "Aktiv", // benefits - "rfp_1": "Előzetes hozzáférés új verziókhoz", - "rfp_2": "2 fiók használata egyszerre", - "rfp_3": "Egyedi üdvözlő üzenet", - "rfp_4": "Korlátlan saját jegyzet és feladat a füzet oldalon", - "rfp_5": "Egyedi jegy ritkaságok", - "rfp_6": "Összesített átlagszámoló", - "rfp_7": "Órarend jegyzetek", - "rfp_8": "Egyedi betütípusok", - "rfp_9": "Korlátlan fiók használata egyszerre", - "rfp_10": "Alkalmazás ikonjának megváltoztatása (v5.1-től)", - "rfp_11": "Live Activity szín", - "rfp_12": "Fejlettebb cél kitűzés", - "rfp_13": "Naptár szinkronizálás", - "rfp_14": "Kedvezmény a reFilc Shop-ban (hamarosan)", - "rfp_15": "Előfizetői rang a Discord szerverünkön", - "rfp_16": "Privát betekintések és információk közelgő újításokról", + "rfp_1": "Frühzeitiger Zugriff auf Updates", + "rfp_2": "Verwendung von zwei Konten gleichzeitig", + "rfp_3": "Individuelle Begrüßungsnachricht", + "rfp_4": + "Unbegrenzte eigene Notizen und Aufgaben auf der Notizbuchseite", + "rfp_5": "Individuelle Notenraritäten", + "rfp_6": "Gesamtdurchschnittsberechner", + "rfp_7": "Stundenplan-Notizen", + "rfp_8": "Individuelle Schriftarten", + "rfp_9": "Unbegrenzte Konten", + "rfp_10": "Anpassung des App-Symbols (ab v5.1)", + "rfp_11": "Änderung der Live-Aktivitätsfarbe", + "rfp_12": "Verbesserter Zielplaner", + "rfp_13": "Importieren Sie Ihren Stundenplan in Ihre Kalender-App", + "rfp_14": "Rabatt im reFilc Shop (bald)", + "rfp_15": "Abonnentenrolle in unserer Discord-Community", + "rfp_16": "Private Leaks und Informationen über kommende Funktionen", + "rfp_17": "Notenexport", // other - "and": " és ", - "every": "Minden ", - "benefit": " előny", + "and": " und ", + "every": "Jeder ", + "benefit": " Vorteil", "show_lifetime": "Für immer Pakete", "more_soon": "Mehr folgt bald...", "faq_dc": - "Az előnyök beváltásához írj nekünk Discord-on privát üzenetet!", + "Um Ihre Vorteile einzulösen, schreiben Sie uns eine private Nachricht auf Discord!", "reactivate": "Bestehendes Abonnement reaktivieren", }, }; diff --git a/refilc_mobile_ui/lib/screens/login/kreten_login.dart b/refilc_mobile_ui/lib/screens/login/kreten_login.dart new file mode 100644 index 0000000..cbafab8 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/login/kreten_login.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc_kreta_api/client/api.dart'; +import 'package:refilc_kreta_api/client/client.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class KretenLoginScreen extends StatefulWidget { + const KretenLoginScreen({super.key}); + + @override + State createState() => _KretenLoginScreenState(); +} + +class _KretenLoginScreenState extends State { + late final WebViewController controller; + var loadingPercentage = 0; + var currentUrl = ''; + + @override + void initState() { + super.initState(); + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageStarted: (url) async { + setState(() { + loadingPercentage = 0; + currentUrl = url; + }); + + List requiredThings = url + .replaceAll( + 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=', + '') + .replaceAll( + '&scope=openid email offline_access kreta-ellenorzo-webapi.public kreta-eugyintezes-webapi.public kreta-fileservice-webapi.public kreta-mobile-global-webapi.public kreta-dkt-webapi.public kreta-ier-webapi.public&state=refilc_student_mobile&session_state=', + ':') + .split(':'); + + String code = requiredThings[0]; + // String sessionState = requiredThings[1]; + + debugPrint('url: $url'); + + // actual login (token grant) logic + Map headers = { + "content-type": "application/x-www-form-urlencoded", + "accept": "*/*", + "user-agent": + "eKretaStudent/264745 CFNetwork/1494.0.7 Darwin/23.4.0", + "code_verifier": "THDUSddKOOndwCkqBtVHvRjh2LK0V2kMyLP2QirqVWQ", + }; + + Map? res = await Provider.of(context, listen: false) + .postAPI(KretaAPI.login, headers: headers, body: { + "code": code, + "redirect_uri": + "https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect", + "client_id": "kreta-ellenorzo-student-mobile-ios", + "grant_type": "authorization_code", + }); + if (res != null) { + print(res); + // if (res.containsKey("error")) { + // if (res["error"] == "invalid_grant") { + // print("ERROR: invalid_grant"); + // return; + // } + // } else { + // if (res.containsKey("access_token")) { + // try { + // Provider.of(context, listen: false).accessToken = + // res["access_token"]; + // Map? studentJson = + // await Provider.of(context, listen: false) + // .getAPI(KretaAPI.student(instituteCode)); + // Student student = Student.fromJson(studentJson!); + // var user = User( + // username: username, + // password: password, + // instituteCode: instituteCode, + // name: student.name, + // student: student, + // role: JwtUtils.getRoleFromJWT(res["access_token"])!, + // ); + + // if (onLogin != null) onLogin(user); + + // // Store User in the database + // await Provider.of(context, listen: false) + // .store + // .storeUser(user); + // Provider.of(context, listen: false) + // .addUser(user); + // Provider.of(context, listen: false) + // .setUser(user.id); + + // // Get user data + // try { + // await Future.wait([ + // Provider.of(context, listen: false) + // .fetch(), + // Provider.of(context, listen: false) + // .fetch(week: Week.current()), + // Provider.of(context, listen: false).fetch(), + // Provider.of(context, listen: false) + // .fetch(), + // Provider.of(context, listen: false) + // .fetchAll(), + // Provider.of(context, listen: false) + // .fetchAllRecipients(), + // Provider.of(context, listen: false).fetch(), + // Provider.of(context, listen: false) + // .fetch(), + // Provider.of(context, listen: false) + // .fetch(), + // ]); + // } catch (error) { + // print("WARNING: failed to fetch user data: $error"); + // } + + // if (onSuccess != null) onSuccess(); + + // return LoginState.success; + // } catch (error) { + // print("ERROR: loginAPI: $error"); + // // maybe check debug mode + // // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error"))); + // return LoginState.failed; + // } + // } + // } + } + }, + onProgress: (progress) { + setState(() { + loadingPercentage = progress; + }); + }, + onPageFinished: (url) { + setState(() { + loadingPercentage = 100; + }); + }, + )) + ..loadRequest( + Uri.parse( + 'https://idp.e-kreta.hu/connect/authorize?prompt=login&nonce=refilc&response_type=code&code_challenge_method=S256&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&code_challenge=Oj_aVMRJHYsv00mrtGJY72NJa7HY54lVnU2Cb4CWbWw&redirect_uri=https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect&client_id=kreta-ellenorzo-student-mobile-ios&state=refilc_student_mobile'), + ); + } + + // Future loadLoginUrl() async { + // String nonceStr = await Provider.of(context, listen: false) + // .getAPI(KretaAPI.nonce, json: false); + + // Nonce nonce = getNonce(nonceStr, ); + // } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: const BackButton(), + title: const Text('e-KRÉTA Bejelentkezés'), + ), + body: Stack( + children: [ + WebViewWidget( + controller: controller, + ), + if (loadingPercentage < 100) + LinearProgressIndicator( + value: loadingPercentage / 100.0, + ), + ], + ), + ); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart index 88057d7..7b9b5de 100644 --- a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart +++ b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart @@ -56,6 +56,13 @@ class AccountView extends StatelessWidget { Detail( title: "parents".plural(user.student.parents.length), description: user.student.parents.join(", ")), + const SizedBox( + height: 10.0, + ), + // Detail( + // title: "parents".i18n, + // description: user.student.parents.join(", ")), + Detail(title: "school".i18n, description: user.student.school.name), ], ), ); diff --git a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart index 7dd4471..ff292a4 100644 --- a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart @@ -9,6 +9,7 @@ extension Localization on String { "class": "Class", "address": "Home address", "parents": "Parents".one("Parent"), + "parents_phone": "Parents' phone number: ".one("Parent"), }, "hu_hu": { "birthdate": "Születési dátum", diff --git a/refilc_mobile_ui/lib/screens/settings/settings_helper.dart b/refilc_mobile_ui/lib/screens/settings/settings_helper.dart index 75959e2..7ae7920 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_helper.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_helper.dart @@ -37,6 +37,9 @@ import 'package:flutter_material_color_picker/flutter_material_color_picker.dart import 'package:refilc/models/icon_pack.dart'; import 'package:refilc/utils/format.dart'; import 'package:refilc_mobile_ui/screens/settings/theme_screen.dart'; +import 'package:refilc_plus/models/premium_scopes.dart'; +import 'package:refilc_plus/providers/plus_provider.dart'; +import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; // import 'package:refilc_plus/models/premium_scopes.dart'; // import 'package:refilc_plus/providers/plus_provider.dart'; // import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; @@ -900,6 +903,15 @@ class _LiveActivityColorSettingState extends State { allowShades: false, selectedColor: settings.liveActivityColor, onMainColorChange: (k) { + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.liveActivityColor)) { + PlusLockedFeaturePopup.show( + context: context, + feature: PremiumFeature.liveActivity, + ); + return; + } + setState(() { currentColor = k as Color; settings.update( diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index 35519be..7bb157d 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -27,6 +27,7 @@ import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu.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/profile_image/profile_image.dart'; +import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart'; // import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; // import 'package:refilc_mobile_ui/common/system_chrome.dart'; @@ -320,6 +321,7 @@ class SettingsScreenState extends State badge: updateProvider.available, role: user.role, profilePictureString: user.picture, + gradeStreak: (user.gradeStreak ?? 0) > 1, backgroundColor: Theme.of(context) .colorScheme .tertiary, //!settings.presentationMode @@ -702,6 +704,46 @@ class SettingsScreenState extends State ], ), + if ((user.gradeStreak ?? 0) > 1) + SplittedPanel( + padding: const EdgeInsets.only( + bottom: 12.0, left: 24.0, right: 24.0), + children: [ + GestureDetector( + onTap: () { + SoonAlert.show(context: context); + }, + child: ListTile( + title: Text( + "grade_streak".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.95), + fontWeight: FontWeight.w500, + ), + ), + subtitle: Text( + "grade_streak_subtitle".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.75), + ), + ), + leading: const Text( + "🔥", + style: TextStyle(fontSize: 22.0), + ), + trailing: Text( + "${user.gradeStreak}", + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.95), + fontWeight: FontWeight.w500, + fontSize: 18.0, + ), + ), + ), + ), + ], + ), + // plus subscribe inline const PlusSettingsInline(), diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index dff0109..c357d3a 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -116,6 +116,14 @@ extension SettingsLocalization on String { "exp_settings": "Export Settings", "manage_subs": "Manage Subscription", "copy_plus_id": "Copy reFilc+ ID", + // grade streak + "grade_streak": "Grade 5 Streak", + "grade_streak_subtitle": "So many 5s in a row?!", + // other + "only_ch_title_font": "Font Only for Titles", + "new_popups": "New Popups", + "export_method": "Export Method", + "grade_exporting": "Grade Exporting", }, "hu_hu": { "personal_details": "Személyes információk", @@ -230,6 +238,14 @@ extension SettingsLocalization on String { "exp_settings": "Beállítások exportálása", "manage_subs": "Előfizetés kezelése", "copy_plus_id": "reFilc+ ID másolása", + // grade streak + "grade_streak": "5-ös sorozat", + "grade_streak_subtitle": "Egymás után ennyi 5-ös?!", + // other + "only_ch_title_font": "Betűtípus csak címekre", + "new_popups": "Új felugró ablakok", + "export_method": "Exportálási mód", + "grade_exporting": "Jegy exportálás", }, "de_de": { "personal_details": "Persönliche Angaben", @@ -344,6 +360,14 @@ extension SettingsLocalization on String { "exp_settings": "Einstellungen exportieren", "manage_subs": "Abonnement verwalten", "copy_plus_id": "reFilc+ ID kopieren", + // grade streak + "grade_streak": "5er-Streak", + "grade_streak_subtitle": "So viele 5er in Folge?!", + // other + "only_ch_title_font": "Schriftart nur für Titel", + "new_popups": "Neue Popups", + "export_method": "Exportmethode", + "grade_exporting": "Noten exportieren", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart b/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart index ac3f6e0..360198b 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; import 'package:refilc_plus/ui/mobile/settings/submenu/calendar_sync.dart'; +import 'package:refilc_plus/ui/mobile/settings/submenu/grade_exporting.dart'; import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; @@ -158,6 +159,16 @@ class ExtrasSettingsScreenState extends State { ), ], ), + SplittedPanel( + padding: const EdgeInsets.only(top: 9.0), + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + MenuGradeExporting( + borderRadius: BorderRadius.circular(12.0), + ), + ], + ), SplittedPanel( padding: const EdgeInsets.only(top: 9.0), cardPadding: const EdgeInsets.all(4.0), diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/grade_colors.dart b/refilc_mobile_ui/lib/screens/settings/submenu/grade_colors.dart new file mode 100644 index 0000000..50445b3 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/settings/submenu/grade_colors.dart @@ -0,0 +1,381 @@ +import 'package:refilc/models/settings.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc/ui/flutter_colorpicker/colorpicker.dart'; +import 'package:refilc/ui/widgets/grade/grade_tile.dart'; +import 'package:refilc_kreta_api/models/grade.dart'; +import 'package:refilc_mobile_ui/screens/settings/theme_screen.dart'; +import 'submenu_screen.i18n.dart'; + +enum SelectedGrade { one, two, three, four, five } + +class GradeColorsSettingsScreen extends StatefulWidget { + const GradeColorsSettingsScreen({super.key}); + + @override + GradeColorsSettingsScreenState createState() => + GradeColorsSettingsScreenState(); +} + +class GradeColorsSettingsScreenState extends State { + late SettingsProvider settingsProvider; + + SelectedGrade currentEditGrade = SelectedGrade.one; + + @override + Widget build(BuildContext context) { + SettingsProvider settingsProvider = Provider.of(context); + + return Scaffold( + appBar: AppBar( + surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, + leading: BackButton( + color: AppColors.of(context).text, + onPressed: () { + setState(() { + // made this cuz else it will be ugly + currentEditGrade = SelectedGrade.one; + }); + Navigator.of(context).pop(); + }, + ), + title: Text( + "grade_colors".i18n, + style: TextStyle(color: AppColors.of(context).text), + ), + actions: [ + IconButton( + onPressed: () { + List colors = List.castFrom(settingsProvider.gradeColors); + var defaultColors = + SettingsProvider.defaultSettings().gradeColors; + colors[currentEditGrade.index] = + defaultColors[currentEditGrade.index]; + settingsProvider.update(gradeColors: colors); + }, + icon: const Icon( + Icons.restore, + size: 26.0, + ), + ), + const SizedBox( + width: 10.0, + ), + ], + ), + body: SizedBox( + width: double.infinity, + height: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.14, + ), + Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(75.0), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .colorScheme + .shadow + .withOpacity(.1), + blurRadius: 10.0, + offset: const Offset(0, 5), + ), + ], + ), + padding: const EdgeInsets.all(6.0), + child: GradeValueWidget( + GradeValue(currentEditGrade.index + 1, '', '', 100), + fill: true, + size: 75.0, + // color: + // settingsProvider.gradeColors[currentEditGrade.index], + ), + ), + // before grades + if (currentEditGrade.index > 0) + Transform.translate( + offset: const Offset(-110, 16.5), + child: GradeValueWidget( + GradeValue(currentEditGrade.index, '', '', 100), + fill: true, + size: 60.0, + // color: + // settingsProvider.gradeColors[currentEditGrade.index], + ), + ), + if (currentEditGrade.index > 1) + Transform.translate( + offset: const Offset(-200, 23), + child: GradeValueWidget( + GradeValue(currentEditGrade.index - 1, '', '', 100), + fill: true, + size: 50.0, + // color: + // settingsProvider.gradeColors[currentEditGrade.index], + ), + ), + // after grades + if (currentEditGrade.index < 4) + Transform.translate( + offset: const Offset(142, 16.5), + child: GradeValueWidget( + GradeValue(currentEditGrade.index + 2, '', '', 100), + fill: true, + size: 60.0, + // color: + // settingsProvider.gradeColors[currentEditGrade.index], + ), + ), + if (currentEditGrade.index < 3) + Transform.translate( + offset: const Offset(245, 23), + child: GradeValueWidget( + GradeValue(currentEditGrade.index + 3, '', '', 100), + fill: true, + size: 50.0, + // color: + // settingsProvider.gradeColors[currentEditGrade.index], + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.14, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: SafeArea( + child: FilcColorPicker( + colorMode: CustomColorMode.grade, + pickerColor: + settingsProvider.gradeColors[currentEditGrade.index], + onColorChanged: (c) { + setState(() { + // update grade color + settingsProvider.update( + gradeColors: settingsProvider.gradeColors + ..[currentEditGrade.index] = c); + }); + }, + onColorChangeEnd: (c, {adaptive}) { + // update grade color + }, + onThemeIdProvided: (t) {}, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 20.0, top: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => setState(() { + currentEditGrade = SelectedGrade.one; + }), + child: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + border: Border.all( + color: currentEditGrade == SelectedGrade.one + ? Theme.of(context).colorScheme.secondary + : Theme.of(context) + .colorScheme + .secondary + .withOpacity(.2), + width: 1.0, + ), + borderRadius: BorderRadius.circular(50.0), + color: currentEditGrade == SelectedGrade.one + ? Theme.of(context).colorScheme.secondary + : null, + ), + child: Center( + child: Text( + '1', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: currentEditGrade == SelectedGrade.one + ? Colors.white + : null, + ), + ), + ), + ), + ), + const SizedBox(width: 10.0), + GestureDetector( + onTap: () => setState(() { + currentEditGrade = SelectedGrade.two; + }), + child: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + border: Border.all( + color: currentEditGrade == SelectedGrade.two + ? Theme.of(context).colorScheme.secondary + : Theme.of(context) + .colorScheme + .secondary + .withOpacity(.2), + width: 1.0, + ), + borderRadius: BorderRadius.circular(50.0), + color: currentEditGrade == SelectedGrade.two + ? Theme.of(context).colorScheme.secondary + : null, + ), + child: Center( + child: Text( + '2', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: currentEditGrade == SelectedGrade.two + ? Colors.white + : null, + ), + ), + ), + ), + ), + const SizedBox(width: 10.0), + GestureDetector( + onTap: () => setState(() { + currentEditGrade = SelectedGrade.three; + }), + child: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + border: Border.all( + color: currentEditGrade == SelectedGrade.three + ? Theme.of(context).colorScheme.secondary + : Theme.of(context) + .colorScheme + .secondary + .withOpacity(.2), + width: 1.0, + ), + borderRadius: BorderRadius.circular(50.0), + color: currentEditGrade == SelectedGrade.three + ? Theme.of(context).colorScheme.secondary + : null, + ), + child: Center( + child: Text( + '3', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: currentEditGrade == SelectedGrade.three + ? Colors.white + : null, + ), + ), + ), + ), + ), + const SizedBox(width: 10.0), + GestureDetector( + onTap: () => setState(() { + currentEditGrade = SelectedGrade.four; + }), + child: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + border: Border.all( + color: currentEditGrade == SelectedGrade.four + ? Theme.of(context).colorScheme.secondary + : Theme.of(context) + .colorScheme + .secondary + .withOpacity(.2), + width: 1.0, + ), + borderRadius: BorderRadius.circular(50.0), + color: currentEditGrade == SelectedGrade.four + ? Theme.of(context).colorScheme.secondary + : null, + ), + child: Center( + child: Text( + '4', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: currentEditGrade == SelectedGrade.four + ? Colors.white + : null, + ), + ), + ), + ), + ), + const SizedBox(width: 10.0), + GestureDetector( + onTap: () => setState(() { + currentEditGrade = SelectedGrade.five; + }), + child: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + border: Border.all( + color: currentEditGrade == SelectedGrade.five + ? Theme.of(context).colorScheme.secondary + : Theme.of(context) + .colorScheme + .secondary + .withOpacity(.2), + width: 1.0, + ), + borderRadius: BorderRadius.circular(50.0), + color: currentEditGrade == SelectedGrade.five + ? Theme.of(context).colorScheme.secondary + : null, + ), + child: Center( + child: Text( + '5', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: currentEditGrade == SelectedGrade.five + ? Colors.white + : null, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart b/refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart index 5a14eeb..63a7f95 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; +import 'package:refilc_mobile_ui/screens/settings/submenu/share_theme_popup.dart'; import 'package:share_plus/share_plus.dart'; class MenuPaintList extends StatelessWidget { @@ -251,18 +252,7 @@ class PaintListScreenState extends State subject: 'share_subj_theme'.i18n, ); } else { - SharedGradeColors gradeColors = await shareProvider - .shareCurrentGradeColors(context); - SharedTheme theme = - await shareProvider.shareCurrentTheme( - context, - gradeColors: gradeColors, - ); - - Share.share( - theme.id, - subject: 'share_subj_theme'.i18n, - ); + ShareThemeDialog.show(context); } }, longPressInstead: true, diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/personalize_screen.dart b/refilc_mobile_ui/lib/screens/settings/submenu/personalize_screen.dart index d9f8bc6..696fa33 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/personalize_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/personalize_screen.dart @@ -30,6 +30,8 @@ import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'grade_colors.dart'; + class MenuPersonalizeSettings extends StatelessWidget { const MenuPersonalizeSettings({ super.key, @@ -459,6 +461,49 @@ class PersonalizeSettingsScreenState extends State ), ], ), + // new popup toggle + SplittedPanel( + padding: const EdgeInsets.only(top: 9.0), + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + PanelButton( + padding: const EdgeInsets.only(left: 14.0, right: 6.0), + onPressed: () async { + settingsProvider.update( + newPopups: !settingsProvider.newPopups); + + setState(() {}); + }, + title: Text( + "new_popups".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity( + settingsProvider.newPopups ? .95 : .25), + ), + ), + leading: Icon( + FeatherIcons.alertOctagon, + size: 22.0, + color: AppColors.of(context).text.withOpacity( + settingsProvider.newPopups ? .95 : .25), + ), + trailing: Switch( + onChanged: (v) async { + settingsProvider.update(newPopups: v); + + setState(() {}); + }, + value: settingsProvider.newPopups, + activeColor: Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), // change subject icons // SplittedPanel( // padding: const EdgeInsets.only(top: 9.0), @@ -499,8 +544,14 @@ class PersonalizeSettingsScreenState extends State children: [ PanelButton( onPressed: () { - SettingsHelper.gradeColors(context); - setState(() {}); + // SettingsHelper.gradeColors(context); + // setState(() {}); + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => + const GradeColorsSettingsScreen(), + ), + ); }, title: Text( "grade_colors".i18n, @@ -670,16 +721,6 @@ class PersonalizeSettingsScreenState extends State children: [ PanelButton( onPressed: () { - if (!Provider.of(context, - listen: false) - .hasScope(PremiumScopes.liveActivityColor)) { - PlusLockedFeaturePopup.show( - context: context, - feature: PremiumFeature.liveActivity, - ); - return; - } - SettingsHelper.liveActivityColor(context); setState(() {}); }, @@ -882,7 +923,7 @@ class PersonalizeSettingsScreenState extends State title: Text('fonts'.i18n), padding: EdgeInsets.zero, cardPadding: const EdgeInsets.all(4.0), - isSeparated: true, + isSeparated: false, children: [ PanelButton( onPressed: () { @@ -920,11 +961,72 @@ class PersonalizeSettingsScreenState extends State ), borderRadius: const BorderRadius.vertical( top: Radius.circular(12.0), + bottom: Radius.circular(6.0), + ), + ), + PanelButton( + padding: const EdgeInsets.only(left: 14.0, right: 6.0), + onPressed: () async { + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.customFont)) { + PlusLockedFeaturePopup.show( + context: context, + feature: PremiumFeature.fontChange); + return; + } + + settingsProvider.update( + titleOnlyFont: !settingsProvider.titleOnlyFont); + Provider.of(context, listen: false) + .changeTheme(settingsProvider.theme, + updateNavbarColor: false); + setState(() {}); + }, + title: Text( + "only_ch_title_font".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity( + settingsProvider.titleOnlyFont ? .95 : .25), + ), + ), + leading: Icon( + Icons.text_increase_rounded, + size: 22.0, + color: AppColors.of(context).text.withOpacity( + settingsProvider.titleOnlyFont ? .95 : .25), + ), + trailing: Switch( + onChanged: (v) async { + if (!Provider.of(context, + listen: false) + .hasScope(PremiumScopes.customFont)) { + PlusLockedFeaturePopup.show( + context: context, + feature: PremiumFeature.fontChange); + return; + } + + settingsProvider.update(titleOnlyFont: v); + Provider.of(context, + listen: false) + .changeTheme(settingsProvider.theme, + updateNavbarColor: false); + setState(() {}); + }, + value: settingsProvider.titleOnlyFont, + activeColor: Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(12.0), ), ), ], ), + // bottom padding + const SizedBox( + height: 20.0, + ), ], ), ), diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart new file mode 100644 index 0000000..92da234 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart @@ -0,0 +1,160 @@ +// ignore_for_file: use_build_context_synchronously, deprecated_member_use + +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:provider/provider.dart'; +// import 'package:refilc/models/settings.dart'; +import 'package:refilc/models/shared_theme.dart'; +import 'package:refilc_kreta_api/providers/share_provider.dart'; +import 'package:refilc_mobile_ui/common/action_button.dart'; +import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; +import 'package:share_plus/share_plus.dart'; +import 'submenu_screen.i18n.dart'; + +class ShareThemeDialog extends StatefulWidget { + const ShareThemeDialog({super.key}); + + static void show(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + title: Text("attention".i18n), + content: Text("share_disclaimer".i18n), + actions: [ + ActionButton( + label: "understand".i18n, + onTap: () async { + Navigator.of(context).pop(); + + showDialog( + context: context, + builder: (context) => const ShareThemeDialog()); + }, + ), + ], + ), + ); + } + + @override + ShareThemeDialogState createState() => ShareThemeDialogState(); +} + +class ShareThemeDialogState extends State { + final _title = TextEditingController(); + bool isPublic = false; + + late ShareProvider shareProvider; + + @override + void initState() { + super.initState(); + shareProvider = Provider.of(context, listen: false); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(14.0))), + contentPadding: const EdgeInsets.only(top: 10.0), + title: Text("share_theme".i18n), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), + child: TextField( + controller: _title, + onEditingComplete: () async {}, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: 'paint_title'.i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _title.text = ''; + }); + }, + ), + ), + ), + ), + SplittedPanel( + children: [ + SwitchListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + value: isPublic, + onChanged: (value) { + setState(() { + isPublic = value; + }); + }, + title: Text("is_public".i18n), + contentPadding: const EdgeInsets.only(left: 15.0, right: 10.0), + ), + ], + ), + ], + ), + actions: [ + TextButton( + child: Text( + "cancel".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + TextButton( + child: Text( + "share_it".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () async { + // share the fucking theme + SharedGradeColors gradeColors = + await shareProvider.shareCurrentGradeColors(context); + SharedTheme theme = await shareProvider.shareCurrentTheme( + context, + gradeColors: gradeColors, + isPublic: isPublic, + displayName: _title.text, + ); + + // save theme id in settings + // Provider.of(context, listen: false) + // .update(currentThemeId: theme.id); + + // close this popup shit + Navigator.of(context).pop(true); + + // show the share popup + Share.share( + theme.id, + subject: 'share_subj_theme'.i18n, + ); + }, + ), + ], + ); + } +} 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 ed30e3d..f3aee86 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 @@ -17,6 +17,17 @@ extension SettingsLocalization on String { "rare": "Rare", "epic": "Epic", "legendary": "Legendary", + // grade colors + "grade_colors": "Grade Colors", + // theme share popup + "share_theme": "Share Paint", + "paint_title": "Paint title...", + "share_it": "Share it!", + "is_public": "Public Paint", + "attention": "Attention!", + "share_disclaimer": + "By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.", + "understand": "I understand", }, "hu_hu": { "general": "Általános", @@ -32,6 +43,17 @@ extension SettingsLocalization on String { "rare": "Ritka", "epic": "Epikus", "legendary": "Legendás", + // grade colors + "grade_colors": "Jegyek színei", + // theme share popup + "share_theme": "Téma megosztása", + "paint_title": "Téma neve...", + "share_it": "Megosztás!", + "is_public": "Nyilvános téma", + "attention": "Figyelem!", + "share_disclaimer": + "A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.", + "understand": "Értem", }, "de_de": { "general": "Allgemeine", @@ -47,6 +69,17 @@ extension SettingsLocalization on String { "rare": "Selten", "epic": "Episch", "legendary": "Legendär", + // grade colors + "grade_colors": "Notenfarben", + // theme share popup + "share_theme": "Thema teilen", + "paint_title": "Thema Titel...", + "share_it": "Teilen!", + "is_public": "Öffentliches Thema", + "attention": "Achtung!", + "share_disclaimer": + "Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.", + "understand": "Ich verstehe", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/theme_screen.dart b/refilc_mobile_ui/lib/screens/settings/theme_screen.dart index 101b0e3..5068d2b 100644 --- a/refilc_mobile_ui/lib/screens/settings/theme_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/theme_screen.dart @@ -1,7 +1,7 @@ // ignore_for_file: use_build_context_synchronously, deprecated_member_use import 'package:refilc/models/settings.dart'; -import 'package:refilc/models/shared_theme.dart'; +// import 'package:refilc/models/shared_theme.dart'; import 'package:refilc/theme/colors/accent.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/observer.dart'; @@ -10,7 +10,7 @@ import 'package:refilc/ui/widgets/message/message_tile.dart'; import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/homework.dart'; import 'package:refilc_kreta_api/models/message.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/filter_bar.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart'; @@ -24,6 +24,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; +import 'package:refilc_mobile_ui/screens/settings/submenu/share_theme_popup.dart'; import 'theme_screen.i18n.dart'; import 'package:share_plus/share_plus.dart'; @@ -44,6 +45,7 @@ enum CustomColorMode { text, icon, enterId, + grade, } class _PremiumCustomAccentColorSettingState @@ -158,6 +160,9 @@ class _PremiumCustomAccentColorSettingState case CustomColorMode.enterId: // do nothing here lol break; + case CustomColorMode.grade: + // do nothing here as well + break; } } @@ -218,6 +223,9 @@ class _PremiumCustomAccentColorSettingState settings.update(customAccentColor: accent, store: store); settings.update(customIconColor: icon, store: store); break; + case CustomColorMode.grade: + // do nothing + break; } } @@ -297,40 +305,48 @@ class _PremiumCustomAccentColorSettingState // ), // ), // ); - showDialog( - context: context, - builder: (context) => WillPopScope( - onWillPop: () async => false, - child: AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0)), - title: Text("attention".i18n), - content: Text("share_disclaimer".i18n), - actions: [ - ActionButton( - label: "understand".i18n, - onTap: () async { - Navigator.of(context).pop(); + // showDialog( + // context: context, + // builder: (context) => WillPopScope( + // onWillPop: () async => false, + // child: AlertDialog( + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12.0)), + // title: Text("attention".i18n), + // content: Text("share_disclaimer".i18n), + // actions: [ + // ActionButton( + // label: "understand".i18n, + // onTap: () async { + // Navigator.of(context).pop(); - SharedGradeColors gradeColors = - await shareProvider - .shareCurrentGradeColors(context); - SharedTheme theme = - await shareProvider.shareCurrentTheme( - context, - gradeColors: gradeColors, - ); + // SharedGradeColors gradeColors = + // await shareProvider + // .shareCurrentGradeColors(context); + // SharedTheme theme = + // await shareProvider.shareCurrentTheme( + // context, + // gradeColors: gradeColors, + // ); - Share.share( - theme.id, - subject: 'share_subj_theme'.i18n, - ); - }, - ), - ], - ), - ), - ); + // Share.share( + // theme.id, + // subject: 'share_subj_theme'.i18n, + // ); + // }, + // ), + // ], + // ), + // ), + // ); + if (settings.currentThemeId != '') { + Share.share( + settings.currentThemeId, + subject: 'share_subj_theme'.i18n, + ); + } else { + ShareThemeDialog.show(context); + } }, icon: const Icon( FeatherIcons.share2, diff --git a/refilc_mobile_ui/pubspec.yaml b/refilc_mobile_ui/pubspec.yaml index 43ffc5e..555383f 100644 --- a/refilc_mobile_ui/pubspec.yaml +++ b/refilc_mobile_ui/pubspec.yaml @@ -71,6 +71,7 @@ dependencies: markdown: ^7.2.2 carousel_slider: ^4.2.1 flutter_portal: ^1.1.4 + webview_flutter: ^4.8.0 dev_dependencies: flutter_lints: ^3.0.1 diff --git a/refilc_plus b/refilc_plus index 1f5cca7..9bd46b8 160000 --- a/refilc_plus +++ b/refilc_plus @@ -1 +1 @@ -Subproject commit 1f5cca7b8e2ac896155a6c494e79fb057628379e +Subproject commit 9bd46b81f230cd094787e1436cd5e8cdee7b5529