From b8e4c4ea3a6599e70d1846d7128fb106c1a704c5 Mon Sep 17 00:00:00 2001
From: hihihaha <71922530+Monke14@users.noreply.github.com>
Date: Sun, 10 Mar 2024 17:38:22 +0100
Subject: [PATCH] finished notification fix

---
 refilc/lib/database/init.dart                 |  11 +-
 refilc/lib/database/store.dart                |   1 +
 refilc/lib/helpers/notification_helper.dart   |  78 ++++-
 .../lib/helpers/notification_helper.i18n.dart |   6 +-
 refilc/lib/main.dart                          |  15 +-
 refilc/lib/utils/navigation_service.dart      |   9 +
 refilc/lib/utils/service_locator.dart         |   8 +
 refilc/pubspec.yaml                           |   3 +
 .../lib/providers/grade_provider.dart         |   6 +-
 .../screens/navigation/navigation_screen.dart |   4 +-
 .../settings/notifications_screen.dart        | 328 ++++++++++--------
 .../settings/notifications_screen.i18n.dart   |   9 +-
 refilc_mobile_ui/pubspec.yaml                 |   5 +-
 13 files changed, 306 insertions(+), 177 deletions(-)
 create mode 100644 refilc/lib/utils/navigation_service.dart
 create mode 100644 refilc/lib/utils/service_locator.dart

diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart
index fef7fd1..ff5c601 100644
--- a/refilc/lib/database/init.dart
+++ b/refilc/lib/database/init.dart
@@ -69,6 +69,7 @@ const userDataDB = DatabaseStruct("user_data", {
   // "subject_lesson_count": String, // non kreta data
   // notifications and surprise grades // non kreta data
   "last_seen_grade": int,
+  "last_seen_surprisegrade": int,
   "last_seen_absence": int,
   "last_seen_message": int,
   "last_seen_lesson": int,
@@ -134,10 +135,11 @@ Future<Database> initDB(DatabaseProvider database) async {
       // renamed teachers // non kreta data
       "renamed_teachers": "{}",
       // "subject_lesson_count": "{}", // non kreta data
-      "last_seen_grade": 0,
-      "last_seen_absence": 0,
-      "last_seen_message": 0,
-      "last_seen_lesson": 0,
+      "last_seen_grade": DateTime.now().millisecondsSinceEpoch,
+      "last_seen_surprisegrade": 0,
+      "last_seen_absence": DateTime.now().millisecondsSinceEpoch,
+      "last_seen_message": DateTime.now().millisecondsSinceEpoch,
+      "last_seen_lesson": DateTime.now().millisecondsSinceEpoch,
       // goal planning // non kreta data
       "goal_plans": "{}",
       "goal_averages": "{}",
@@ -215,3 +217,4 @@ Future<void> migrateDB(
     print("INFO: Database migrated");
   }
 }
+
diff --git a/refilc/lib/database/store.dart b/refilc/lib/database/store.dart
index 2a64bfb..4b0f23f 100644
--- a/refilc/lib/database/store.dart
+++ b/refilc/lib/database/store.dart
@@ -136,6 +136,7 @@ class UserDatabaseStore {
     await db.update("user_data", {"last_seen_${category.name}": lastSeenDate},
         where: "id = ?", whereArgs: [userId]);
   }
+  
 
   // renamed things
   Future<void> storeRenamedSubjects(Map<String, String> subjects,
diff --git a/refilc/lib/helpers/notification_helper.dart b/refilc/lib/helpers/notification_helper.dart
index 4ae83df..fa1ed8a 100644
--- a/refilc/lib/helpers/notification_helper.dart
+++ b/refilc/lib/helpers/notification_helper.dart
@@ -1,9 +1,12 @@
+import 'package:flutter/foundation.dart';
 import 'package:refilc/api/providers/database_provider.dart';
 import 'package:refilc/api/providers/status_provider.dart';
 import 'package:refilc/api/providers/user_provider.dart';
 import 'package:refilc/models/settings.dart';
 import 'package:refilc/helpers/notification_helper.i18n.dart';
 import 'package:refilc/models/user.dart';
+import 'package:refilc/utils/navigation_service.dart';
+import 'package:refilc/utils/service_locator.dart';
 import 'package:refilc_kreta_api/client/api.dart';
 import 'package:refilc_kreta_api/client/client.dart';
 import 'package:refilc_kreta_api/models/absence.dart';
@@ -18,8 +21,10 @@ import 'package:i18n_extension/i18n_widget.dart';
 import 'package:intl/intl.dart';
 import 'package:refilc_kreta_api/models/message.dart';
 
+// if you want to add a new category, also add it to the DB or else the app will probably crash
 enum LastSeenCategory {
   grade,
+  surprisegrade,
   absence,
   message,
   lesson
@@ -91,20 +96,14 @@ class NotificationsHelper {
         .then((grades) async {
       DateTime lastSeenGrade = await database.userQuery.lastSeen(
           userId: currentuserProvider.id!, category: LastSeenCategory.grade);
-      lastSeenGrade = lastSeenGrade.subtract(const Duration(minutes: 2)); // needed as lastSeenGrade somehow will be a bit in the future
 
       // loop through grades and see which hasn't been seen yet
       for (Grade grade in grades) {
         // if grade is not a normal grade (1-5), don't show it
         if ([1, 2, 3, 4, 5].contains(grade.value.value)) {
           // if the grade was added over a week ago, don't show it to avoid notification spam
-          // it worked in reverse, cuz someone added a * -1 to it, but it has been fixed now :D
-          // old code below
-          // if (grade.seenDate.isAfter(lastSeenGrade) &&
-          //     grade.date.difference(DateTime.now()).inDays * -1 < 7) {
-          // new code from here :P
-          if (grade.seenDate.isAfter(lastSeenGrade) &&
-              grade.date.difference(DateTime.now()).inDays < 7) {
+          if (grade.date.isAfter(lastSeenGrade) &&
+              DateTime.now().difference(grade.date).inDays < 7) {
             // send notificiation about new grade
             AndroidNotificationDetails androidNotificationDetails =
                 AndroidNotificationDetails(
@@ -132,6 +131,7 @@ class NotificationsHelper {
                   ],
                 ),
                 notificationDetails,
+                payload: "grades"
               );
             } else {
               // multiple users are added, also display student name
@@ -149,6 +149,7 @@ class NotificationsHelper {
                   ],
                 ),
                 notificationDetails,
+                payload: "grades"
               );
             }
           }
@@ -200,6 +201,7 @@ class NotificationsHelper {
               ],
             ),
             notificationDetails,
+            payload: "absences"
           );
         } else {
           await flutterLocalNotificationsPlugin.show(
@@ -217,6 +219,7 @@ class NotificationsHelper {
               ],
             ),
             notificationDetails,
+            payload: "absences"
           );
         }
       }
@@ -271,6 +274,7 @@ class NotificationsHelper {
                 message.author,
                 message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
                 notificationDetails,
+                payload: "messages",
               );
             } else {
               await flutterLocalNotificationsPlugin.show(
@@ -278,6 +282,7 @@ class NotificationsHelper {
                 "(${currentuserProvider.displayName!}) ${message.author}",
                 message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
                 notificationDetails,
+                payload: "messages",
               );
             }
           }
@@ -297,8 +302,12 @@ class NotificationsHelper {
 
         DateTime lastSeenLesson = await database.userQuery.lastSeen(
             userId: currentuserProvider.id!, category: LastSeenCategory.lesson);
+        Lesson? latestLesson;
 
         for (Lesson lesson in apilessons) {
+          if((lesson.status?.name != "Elmaradt" || lesson.substituteTeacher?.name != "") && lesson.date.isAfter(latestLesson?.start ?? DateTime(1970))) {
+              latestLesson = lesson;
+            }
           if (lesson.date.isAfter(lastSeenLesson)) {
             AndroidNotificationDetails androidNotificationDetails =
                 AndroidNotificationDetails(
@@ -329,6 +338,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable"
                       );
                       break;
                     }
@@ -345,6 +355,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable"
                       );
                       break;
                     }
@@ -361,11 +372,12 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable"
                       );
                       break;
                     }
                 }
-              } else if (lesson.substituteTeacher?.name != "") {
+              } else if (lesson.substituteTeacher?.name != "" && lesson.substituteTeacher != null) {
                 switch (I18n.localeStr) {
                   case "en_en":
                     {
@@ -383,6 +395,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -402,6 +415,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -421,6 +435,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -443,6 +458,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -460,6 +476,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -477,6 +494,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -500,6 +518,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -520,6 +539,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -540,6 +560,7 @@ class NotificationsHelper {
                           ],
                         ),
                         notificationDetails,
+                        payload: "timetable",
                       );
                       break;
                     }
@@ -548,7 +569,44 @@ class NotificationsHelper {
             }
           }
         }
-        await database.userStore.storeLastSeen(DateTime.now(),
+        // lesson.date does not contain time, only the date
+        await database.userStore.storeLastSeen(latestLesson?.start ?? DateTime.now(),
             userId: currentuserProvider.id!, category: LastSeenCategory.lesson);
       }
+      
+      // Called when the user taps a notification
+      void onDidReceiveNotificationResponse(NotificationResponse notificationResponse) async {
+        final String? payload = notificationResponse.payload;
+        if (notificationResponse.payload != null) {
+          debugPrint('notification payload: $payload');
+        }
+        switch(payload) {
+          case "timetable":
+            locator<NavigationService>().navigateTo("timetable");
+            break;
+          case "grades":
+            locator<NavigationService>().navigateTo("grades");
+            break;
+          case "messages":
+            locator<NavigationService>().navigateTo("messages");
+            break;
+          case "absences":
+            locator<NavigationService>().navigateTo("absences");
+            break;
+          case "settings":
+            locator<NavigationService>().navigateTo("settings");
+            break;
+          default:
+            break;
+        }
+      }
+
+      // Set all notification categories to seen
+      Future<void> setAllCategoriesSeen(UserProvider userProvider) async {
+        if(userProvider.id != null) {
+          for(LastSeenCategory category in LastSeenCategory.values) {
+            await database.userStore.storeLastSeen(DateTime.now(), userId: userProvider.id!, category: category);
+          }
+        }
+      }
     }
diff --git a/refilc/lib/helpers/notification_helper.i18n.dart b/refilc/lib/helpers/notification_helper.i18n.dart
index bc4b866..49db9f7 100644
--- a/refilc/lib/helpers/notification_helper.i18n.dart
+++ b/refilc/lib/helpers/notification_helper.i18n.dart
@@ -47,9 +47,9 @@ extension Localization on String {
   String get i18n {
     // very hacky way to get app language in notifications
     // i18n does not like being in background functions (it cannot retrieve locale sometimes)
-    final DatabaseProvider _databaseProvider = DatabaseProvider();
-    _databaseProvider.init().then((value) {
-      _databaseProvider.query.getSettings(_databaseProvider).then((settings) {
+    final DatabaseProvider databaseProvider = DatabaseProvider();
+    databaseProvider.init().then((value) {
+      databaseProvider.query.getSettings(databaseProvider).then((settings) {
       return localize(this, _t, locale: "${settings.language}_${settings.language.toUpperCase()}");
     });
     });
diff --git a/refilc/lib/main.dart b/refilc/lib/main.dart
index f33a476..327f25e 100644
--- a/refilc/lib/main.dart
+++ b/refilc/lib/main.dart
@@ -10,6 +10,8 @@ import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:refilc/app.dart';
 import 'package:flutter/services.dart';
+import 'package:refilc/utils/navigation_service.dart';
+import 'package:refilc/utils/service_locator.dart';
 import 'package:refilc_mobile_ui/screens/error_screen.dart';
 import 'package:refilc_mobile_ui/screens/error_report_screen.dart';
 import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@@ -24,19 +26,22 @@ void main() async {
   // ignore: deprecated_member_use
   binding.renderView.automaticSystemUiAdjustment = false;
   SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
+  // navigation
+  setupLocator();
+
   // Startup
   Startup startup = Startup();
   await startup.start();
 
   // Custom error page
   ErrorWidget.builder = errorBuilder;
-
-  BackgroundFetch.registerHeadlessTask(backgroundHeadlessTask);
-
   // initialize stripe key
   stripe.Stripe.publishableKey =
       'pk_test_51Oo7iUBS0FxsTGxKjGZSQqzDKWHY5ZFYM9XeI0qSdIh2w8jWy6GhHlYpT7GLTzgpl1xhE5YP4BXpA4gMZqPmgMId00cGFYFzbh';
 
+
+  BackgroundFetch.registerHeadlessTask(backgroundHeadlessTask);
+
   // Run App
   runApp(App(
     database: startup.database,
@@ -58,6 +63,9 @@ class Startup {
     settings = await database.query.getSettings(database);
     user = await database.query.getUsers(settings);
 
+    // Set all notification categories to seen to avoid having notifications that the user has already seen in the app
+    NotificationsHelper().setAllCategoriesSeen(user);
+
     late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
     // Notifications setup
     if (!kIsWeb) {
@@ -118,6 +126,7 @@ class Startup {
       // Initialize notifications
       await flutterLocalNotificationsPlugin.initialize(
         initializationSettings,
+        onDidReceiveNotificationResponse: NotificationsHelper().onDidReceiveNotificationResponse,
       );
     }
 
diff --git a/refilc/lib/utils/navigation_service.dart b/refilc/lib/utils/navigation_service.dart
new file mode 100644
index 0000000..ee8a479
--- /dev/null
+++ b/refilc/lib/utils/navigation_service.dart
@@ -0,0 +1,9 @@
+import 'package:flutter/material.dart';
+
+class NavigationService {
+  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
+
+  Future<dynamic> navigateTo(String routeName) {
+    return navigatorKey.currentState!.pushNamed(routeName);
+  }
+}
\ No newline at end of file
diff --git a/refilc/lib/utils/service_locator.dart b/refilc/lib/utils/service_locator.dart
new file mode 100644
index 0000000..0ca94b7
--- /dev/null
+++ b/refilc/lib/utils/service_locator.dart
@@ -0,0 +1,8 @@
+import 'package:get_it/get_it.dart';
+import 'package:refilc/utils/navigation_service.dart';
+
+GetIt locator = GetIt.instance;
+
+void setupLocator() {
+  locator.registerLazySingleton(() => NavigationService());
+}
\ No newline at end of file
diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml
index 918146e..6d05e57 100644
--- a/refilc/pubspec.yaml
+++ b/refilc/pubspec.yaml
@@ -78,6 +78,7 @@ dependencies:
   maps_launcher: ^2.2.0
   google_fonts: ^6.1.0
   flutter_stripe: ^10.0.0
+  get_it: ^7.6.7
 
 dev_dependencies:
   flutter_lints: ^3.0.1
@@ -97,6 +98,7 @@ flutter:
     - assets/images/subject_covers/
     - assets/launch_icons/
     - assets/images/ext_logo/
+    - assets/svg/menu_icons/
 
   fonts:
     - family: FilcIcons
@@ -161,6 +163,7 @@ flutter:
           weight: 700
           style: italic
 
+
 flutter_launcher_icons:
   image_path: assets/icons/ic_android.png
   android: true
diff --git a/refilc_kreta_api/lib/providers/grade_provider.dart b/refilc_kreta_api/lib/providers/grade_provider.dart
index 2d61b28..3ab9701 100644
--- a/refilc_kreta_api/lib/providers/grade_provider.dart
+++ b/refilc_kreta_api/lib/providers/grade_provider.dart
@@ -50,7 +50,7 @@ class GradeProvider with ChangeNotifier {
     String? userId = _user.id;
     if (userId != null) {
       final userStore = _database.userStore;
-      userStore.storeLastSeen(DateTime.now(), userId: userId, category: LastSeenCategory.grade);
+      userStore.storeLastSeen(DateTime.now(), userId: userId, category: LastSeenCategory.surprisegrade);
       _lastSeen = DateTime.now();
     }
   }
@@ -59,7 +59,7 @@ class GradeProvider with ChangeNotifier {
     String? userId = _user.id;
     if (userId != null) {
       final userStore = _database.userStore;
-      userStore.storeLastSeen(DateTime(1969), userId: userId, category: LastSeenCategory.grade);
+      userStore.storeLastSeen(DateTime(1969), userId: userId, category: LastSeenCategory.surprisegrade);
       _lastSeen = DateTime(1969);
     }
   }
@@ -75,7 +75,7 @@ class GradeProvider with ChangeNotifier {
       await convertBySettings();
       _groupAvg = await userQuery.getGroupAverages(userId: userId);
       notifyListeners();
-      DateTime lastSeenDB = await userQuery.lastSeen(userId: userId, category: LastSeenCategory.grade);
+      DateTime lastSeenDB = await userQuery.lastSeen(userId: userId, category: LastSeenCategory.surprisegrade);
       if (lastSeenDB.millisecondsSinceEpoch == 0 ||
           lastSeenDB.year == 0 ||
           !_settings.gradeOpeningFun) {
diff --git a/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart b/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart
index 4659feb..22ca045 100644
--- a/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart
+++ b/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart
@@ -2,6 +2,8 @@ import 'package:refilc/api/providers/update_provider.dart';
 import 'package:refilc/helpers/quick_actions.dart';
 import 'package:refilc/models/settings.dart';
 import 'package:refilc/theme/observer.dart';
+import 'package:refilc/utils/navigation_service.dart';
+import 'package:refilc/utils/service_locator.dart';
 import 'package:refilc_kreta_api/client/client.dart';
 import 'package:refilc_kreta_api/providers/grade_provider.dart';
 import 'package:refilc_mobile_ui/common/system_chrome.dart';
@@ -42,7 +44,7 @@ class NavigationScreenState extends State<NavigationScreen>
     with WidgetsBindingObserver {
   late NavigationRoute selected;
   List<String> initializers = [];
-  final _navigatorState = GlobalKey<NavigatorState>();
+  final _navigatorState = locator<NavigationService>().navigatorKey;
 
   late SettingsProvider settings;
   late NewsProvider newsProvider;
diff --git a/refilc_mobile_ui/lib/screens/settings/notifications_screen.dart b/refilc_mobile_ui/lib/screens/settings/notifications_screen.dart
index ba4b618..b3cdbe2 100644
--- a/refilc_mobile_ui/lib/screens/settings/notifications_screen.dart
+++ b/refilc_mobile_ui/lib/screens/settings/notifications_screen.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
 import 'package:refilc/api/providers/database_provider.dart';
 import 'package:refilc/api/providers/user_provider.dart';
 import 'package:refilc/helpers/notification_helper.dart';
@@ -5,12 +6,12 @@ import 'package:refilc/models/settings.dart';
 import 'package:refilc/models/user.dart';
 import 'package:refilc/theme/colors/colors.dart';
 import 'package:refilc_mobile_ui/common/beta_chip.dart';
-import 'package:refilc_mobile_ui/common/panel/panel.dart';
 import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_feather_icons/flutter_feather_icons.dart';
 import 'package:provider/provider.dart';
+import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
 import 'notifications_screen.i18n.dart';
 
 class MenuNotifications extends StatelessWidget {
@@ -57,13 +58,15 @@ class MenuNotifications extends StatelessWidget {
 class NotificationsScreen extends StatelessWidget {
   const NotificationsScreen({super.key});
 
-  void setAllAsSeen(BuildContext context) {
-    // Set all notification categories as seen to avoid spamming the user with notifications when they turn on notifications
-    DatabaseProvider database = Provider.of<DatabaseProvider>(context, listen: false);
+  // Set all notification categories as seen to avoid spamming the user with notifications when they turn on notifications
+  void setAll(BuildContext context, DateTime date) {
+    DatabaseProvider database =
+        Provider.of<DatabaseProvider>(context, listen: false);
     User? user = Provider.of<UserProvider>(context, listen: false).user;
-    if(user != null) {
-      for(LastSeenCategory category in LastSeenCategory.values) {
-        database.userStore.storeLastSeen(DateTime.now(), userId: user.id, category: category);
+    if (user != null) {
+      for (LastSeenCategory category in LastSeenCategory.values) {
+        database.userStore
+            .storeLastSeen(date, userId: user.id, category: category);
       }
     }
   }
@@ -84,159 +87,188 @@ class NotificationsScreen extends StatelessWidget {
       body: SingleChildScrollView(
         child: Padding(
           padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
-          child: Panel(
-            child: Column(
-              children: [
-                Material(
-                  type: MaterialType.transparency,
-                  child: SwitchListTile(
-                    shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(12.0)),
-                    value: settings.notificationsGradesEnabled,
-                    onChanged: (v) {
-                        settings.update(notificationsGradesEnabled: v);
-                        setAllAsSeen(context);
+          child: Column(
+            children: [
+              SplittedPanel(
+                padding: const EdgeInsets.only(top: 8.0),
+                cardPadding: const EdgeInsets.all(4.0),
+                isSeparated: true,
+                children: [
+                  PanelButton(
+                    padding: const EdgeInsets.only(left: 14.0, right: 6.0),
+                    onPressed: () {
+                      settings.update(
+                          notificationsEnabled:
+                              !settings.notificationsGradesEnabled);
+                      setAll(context, DateTime.now());
                     },
-                    title: Row(
-                      children: [
-                        Icon(
-                          FeatherIcons.bookmark,
-                          color: settings.notificationsGradesEnabled
-                              ? Theme.of(context).colorScheme.secondary
-                              : AppColors.of(context).text.withOpacity(.25),
-                        ),
-                        const SizedBox(width: 14.0),
-                        Expanded(
-                          child: Text(
-                            "grades".i18n,
-                            style: TextStyle(
-                              fontWeight: FontWeight.w600,
-                              fontSize: 16.0,
-                              color: AppColors.of(context).text.withOpacity(
-                                    settings.notificationsGradesEnabled
-                                        ? 1.0
-                                        : .5,
-                                  ),
-                            ),
-                          ),
-                        ),
-                      ],
+                    title: Text(
+                      "grades".i18n,
+                      style: TextStyle(
+                        color: AppColors.of(context).text.withOpacity(
+                            settings.notificationsGradesEnabled ? .95 : .25),
+                      ),
+                    ),
+                    leading: Icon(
+                      FeatherIcons.bookmark,
+                      size: 22.0,
+                      color: AppColors.of(context).text.withOpacity(
+                          settings.notificationsGradesEnabled ? .95 : .25),
+                    ),
+                    trailing: Switch(
+                      onChanged: (v) =>
+                          settings.update(notificationsGradesEnabled: v),
+                      value: settings.notificationsGradesEnabled,
+                      activeColor: Theme.of(context).colorScheme.secondary,
+                    ),
+                    borderRadius: const BorderRadius.vertical(
+                      top: Radius.circular(12.0),
+                      bottom: Radius.circular(12.0),
                     ),
                   ),
-                ),
-                Material(
-                  type: MaterialType.transparency,
-                  child: SwitchListTile(
-                    shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(12.0)),
-                    value: settings.notificationsAbsencesEnabled,
-                    onChanged: (v) {
-                        settings.update(notificationsAbsencesEnabled: v);
-                        setAllAsSeen(context);
+                ],
+              ),
+              SplittedPanel(
+                padding: const EdgeInsets.only(top: 8.0),
+                cardPadding: const EdgeInsets.all(4.0),
+                isSeparated: true,
+                children: [
+                  PanelButton(
+                    padding: const EdgeInsets.only(left: 14.0, right: 6.0),
+                    onPressed: () {
+                      settings.update(
+                          notificationsEnabled:
+                              !settings.notificationsAbsencesEnabled);
+                      setAll(context, DateTime.now());
                     },
-                    title: Row(
-                      children: [
-                        Icon(
-                          FeatherIcons.clock,
-                          color: settings.notificationsAbsencesEnabled
-                              ? Theme.of(context).colorScheme.secondary
-                              : AppColors.of(context).text.withOpacity(.25),
-                        ),
-                        const SizedBox(width: 14.0),
-                        Expanded(
-                          child: Text(
-                            "absences".i18n,
-                            style: TextStyle(
-                              fontWeight: FontWeight.w600,
-                              fontSize: 16.0,
-                              color: AppColors.of(context).text.withOpacity(
-                                    settings.notificationsAbsencesEnabled
-                                        ? 1.0
-                                        : .5,
-                                  ),
-                            ),
-                          ),
-                        ),
-                      ],
+                    title: Text(
+                      "absences".i18n,
+                      style: TextStyle(
+                        color: AppColors.of(context).text.withOpacity(
+                            settings.notificationsAbsencesEnabled ? .95 : .25),
+                      ),
+                    ),
+                    leading: Icon(
+                      FeatherIcons.clock,
+                      size: 22.0,
+                      color: AppColors.of(context).text.withOpacity(
+                          settings.notificationsAbsencesEnabled ? .95 : .25),
+                    ),
+                    trailing: Switch(
+                      onChanged: (v) =>
+                          settings.update(notificationsAbsencesEnabled: v),
+                      value: settings.notificationsAbsencesEnabled,
+                      activeColor: Theme.of(context).colorScheme.secondary,
+                    ),
+                    borderRadius: const BorderRadius.vertical(
+                      top: Radius.circular(12.0),
+                      bottom: Radius.circular(12.0),
                     ),
                   ),
-                ),
-                Material(
-                  type: MaterialType.transparency,
-                  child: SwitchListTile(
-                    shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(12.0)),
-                    value: settings.notificationsMessagesEnabled,
-                    onChanged: (v) {
-                        settings.update(notificationsMessagesEnabled: v);
-                        setAllAsSeen(context);
+                ],
+              ),
+              SplittedPanel(
+                padding: const EdgeInsets.only(top: 8.0),
+                cardPadding: const EdgeInsets.all(4.0),
+                isSeparated: true,
+                children: [
+                  PanelButton(
+                    padding: const EdgeInsets.only(left: 14.0, right: 6.0),
+                    onPressed: () {
+                      settings.update(
+                          notificationsEnabled:
+                              !settings.notificationsMessagesEnabled);
+                      setAll(context, DateTime.now());
                     },
-                    title: Row(
-                      children: [
-                        Icon(
-                          FeatherIcons.messageSquare,
-                          color: settings.notificationsMessagesEnabled
-                              ? Theme.of(context).colorScheme.secondary
-                              : AppColors.of(context).text.withOpacity(.25),
-                        ),
-                        const SizedBox(width: 14.0),
-                        Expanded(
-                          child: Text(
-                            "messages".i18n,
-                            style: TextStyle(
-                              fontWeight: FontWeight.w600,
-                              fontSize: 16.0,
-                              color: AppColors.of(context).text.withOpacity(
-                                    settings.notificationsMessagesEnabled
-                                        ? 1.0
-                                        : .5,
-                                  ),
-                            ),
-                          ),
-                        ),
-                      ],
+                    title: Text(
+                      "messages".i18n,
+                      style: TextStyle(
+                        color: AppColors.of(context).text.withOpacity(
+                            settings.notificationsMessagesEnabled ? .95 : .25),
+                      ),
+                    ),
+                    leading: Icon(
+                      FeatherIcons.messageSquare,
+                      size: 22.0,
+                      color: AppColors.of(context).text.withOpacity(
+                          settings.notificationsMessagesEnabled ? .95 : .25),
+                    ),
+                    trailing: Switch(
+                      onChanged: (v) =>
+                          settings.update(notificationsMessagesEnabled: v),
+                      value: settings.notificationsMessagesEnabled,
+                      activeColor: Theme.of(context).colorScheme.secondary,
+                    ),
+                    borderRadius: const BorderRadius.vertical(
+                      top: Radius.circular(12.0),
+                      bottom: Radius.circular(12.0),
                     ),
                   ),
-                ),
-                Material(
-                  type: MaterialType.transparency,
-                  child: SwitchListTile(
-                    shape: RoundedRectangleBorder(
-                        borderRadius: BorderRadius.circular(12.0)),
-                    value: settings.notificationsLessonsEnabled,
-                    onChanged: (v) {
-                        settings.update(notificationsLessonsEnabled: v);
-                        setAllAsSeen(context);
+                ],
+              ),
+              SplittedPanel(
+                padding: const EdgeInsets.only(top: 8.0),
+                cardPadding: const EdgeInsets.all(4.0),
+                isSeparated: true,
+                children: [
+                  PanelButton(
+                    padding: const EdgeInsets.only(left: 14.0, right: 6.0),
+                    onPressed: () {
+                      settings.update(
+                          notificationsEnabled:
+                              !settings.notificationsLessonsEnabled);
+                      setAll(context, DateTime.now());
                     },
-                    title: Row(
-                      children: [
-                        Icon(
-                          FeatherIcons.calendar,
-                          color: settings.notificationsLessonsEnabled
-                              ? Theme.of(context).colorScheme.secondary
-                              : AppColors.of(context).text.withOpacity(.25),
-                        ),
-                        const SizedBox(width: 14.0),
-                        Expanded(
-                          child: Text(
-                            "lessons".i18n,
-                            style: TextStyle(
-                              fontWeight: FontWeight.w600,
-                              fontSize: 16.0,
-                              color: AppColors.of(context).text.withOpacity(
-                                    settings.notificationsLessonsEnabled
-                                        ? 1.0
-                                        : .5,
-                                  ),
-                            ),
-                          ),
-                        ),
-                      ],
+                    title: Text(
+                      "lessons".i18n,
+                      style: TextStyle(
+                        color: AppColors.of(context).text.withOpacity(
+                            settings.notificationsLessonsEnabled ? .95 : .25),
+                      ),
+                    ),
+                    leading: Icon(
+                      FeatherIcons.bookmark,
+                      size: 22.0,
+                      color: AppColors.of(context).text.withOpacity(
+                          settings.notificationsLessonsEnabled ? .95 : .25),
+                    ),
+                    trailing: Switch(
+                      onChanged: (v) =>
+                          settings.update(notificationsLessonsEnabled: v),
+                      value: settings.notificationsLessonsEnabled,
+                      activeColor: Theme.of(context).colorScheme.secondary,
+                    ),
+                    borderRadius: const BorderRadius.vertical(
+                      top: Radius.circular(12.0),
+                      bottom: Radius.circular(12.0),
                     ),
                   ),
-                ),
-              ],
-            ),
+                ],
+              ),
+              // only used for debugging, pressing **will** cause notification spam
+              kDebugMode
+                  ? SplittedPanel(
+                      padding: const EdgeInsets.only(top: 9.0),
+                      cardPadding: const EdgeInsets.all(4.0),
+                      isSeparated: true,
+                      children: [
+                        PanelButton(
+                          onPressed: () => setAll(
+                              context, DateTime(1970, 1, 1, 0, 0, 0, 0, 0)),
+                          title: Text("set_all_as_unseen".i18n),
+                          leading: Icon(
+                            FeatherIcons.mail,
+                            size: 22.0,
+                            color: AppColors.of(context).text.withOpacity(0.95),
+                          ),
+                          borderRadius: const BorderRadius.vertical(
+                              top: Radius.circular(12.0),
+                              bottom: Radius.circular(4.0)),
+                        )
+                      ],
+                    )
+                  : const SizedBox.shrink(),
+            ],
           ),
         ),
       ),
diff --git a/refilc_mobile_ui/lib/screens/settings/notifications_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/notifications_screen.i18n.dart
index c9d0087..310779f 100644
--- a/refilc_mobile_ui/lib/screens/settings/notifications_screen.i18n.dart
+++ b/refilc_mobile_ui/lib/screens/settings/notifications_screen.i18n.dart
@@ -8,7 +8,8 @@ extension SettingsLocalization on String {
           "grades": "Grades",
           "absences": "Absences",
           "messages": "Messages",
-          "lessons": "Lessons"
+          "lessons": "Lessons",
+          "set_all_as_unseen": "Set all as unseen",
           
         },
         "hu_hu": {
@@ -16,14 +17,16 @@ extension SettingsLocalization on String {
           "grades": "Jegyek",
           "absences": "Hiányzások",
           "messages": "Üzenetek",
-          "lessons": "Órák"
+          "lessons": "Órák",
+          "set_all_as_unseen": "Összes kategória beállítása olvasatlannak",
         },
         "de_de": {
           "notifications_screen": "Mitteilung",
           "grades": "Noten",
           "absences": "Fehlen",
           "messages": "Nachrichten",
-          "lessons": "Unterricht"
+          "lessons": "Unterricht",
+          "set_all_as_unseen": "Alle als ungelesen einstellen",
         },
       };
 
diff --git a/refilc_mobile_ui/pubspec.yaml b/refilc_mobile_ui/pubspec.yaml
index b630c65..36cbc33 100644
--- a/refilc_mobile_ui/pubspec.yaml
+++ b/refilc_mobile_ui/pubspec.yaml
@@ -67,9 +67,10 @@ dependencies:
   flutter_stripe: ^10.0.0
   flutter_any_logo: ^1.1.1
   custom_sliding_segmented_control: ^1.8.1
-  
+  get_it: ^7.6.7
+
 dev_dependencies:
   flutter_lints: ^3.0.1
 
 flutter:
-  uses-material-design: true
\ No newline at end of file
+  uses-material-design: true