diff --git a/app/build.gradle b/app/build.gradle
index 1d6e56b1..214eee79 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
 apply plugin: 'kotlin-parcelize'
 apply plugin: 'kotlin-kapt'
 apply plugin: 'dagger.hilt.android.plugin'
@@ -14,23 +15,22 @@ apply from: 'sonarqube.gradle'
 apply from: 'hooks.gradle'
 
 android {
-    compileSdkVersion 30
+    compileSdkVersion 31
 
     defaultConfig {
         applicationId "io.github.wulkanowy"
         testApplicationId "io.github.tests.wulkanowy"
         minSdkVersion 21
-        targetSdkVersion 30
-        versionCode 97
-        versionName "1.3.0"
+        targetSdkVersion 31
+        versionCode 98
+        versionName "1.4.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        vectorDrawables.useSupportLibrary = true
 
         resValue "string", "app_name", "Wulkanowy"
-        buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
 
         manifestPlaceholders = [
-                firebase_enabled: project.hasProperty("enableFirebase")
+                firebase_enabled: project.hasProperty("enableFirebase"),
+                admob_project_id: ""
         ]
         javaCompileOptions {
             annotationProcessorOptions {
@@ -40,6 +40,14 @@ android {
                 ]
             }
         }
+
+        buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
+
+        if (System.env.SET_BUILD_TIMESTAMP) {
+            buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
+        } else {
+            buildConfigField "long", "BUILD_TIMESTAMP", "1486235849000"
+        }
     }
 
     sourceSets {
@@ -62,12 +70,14 @@ android {
             shrinkResources true
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
             signingConfig signingConfigs.release
+            buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
         }
         debug {
-            resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
+            resValue "string", "app_name", "Wulkanowy DEV"
             applicationIdSuffix ".dev"
             versionNameSuffix "-dev"
             ext.enableCrashlytics = project.hasProperty("enableFirebase")
+            buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
         }
     }
 
@@ -76,23 +86,21 @@ android {
     productFlavors {
         hms {
             dimension "platform"
-            manifestPlaceholders = [
-                    install_channel: "AppGallery"
-            ]
+            manifestPlaceholders = [install_channel: "AppGallery"]
         }
 
         play {
             dimension "platform"
             manifestPlaceholders = [
-                    install_channel: "Google Play"
+                    install_channel : "Google Play",
+                    admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
             ]
+            buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
         }
 
         fdroid {
             dimension "platform"
-            manifestPlaceholders = [
-                    install_channel: "F-Droid"
-            ]
+            manifestPlaceholders = [install_channel: "F-Droid"]
         }
     }
 
@@ -141,8 +149,8 @@ kapt {
 
 play {
     defaultToAppBundles = false
-    track = 'production'
-    updatePriority = 3
+    track = 'beta'
+    updatePriority = 1
     enabled.set(false)
 }
 
@@ -157,27 +165,28 @@ huaweiPublish {
 }
 
 ext {
-    work_manager = "2.6.0"
+    work_manager = "2.7.0"
     android_hilt = "1.0.0"
     room = "2.3.0"
     chucker = "3.5.2"
     mockk = "1.12.0"
-    moshi = "1.12.0"
+    coroutines = "1.5.2"
 }
 
 dependencies {
-    implementation "io.github.wulkanowy:sdk:1.3.0"
+    implementation "io.github.wulkanowy:sdk:1.4.0"
 
     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
 
-    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
+    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
+    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
 
-    implementation "androidx.core:core-ktx:1.6.0"
-    implementation "androidx.activity:activity-ktx:1.3.1"
-    implementation "androidx.appcompat:appcompat:1.3.1"
-    implementation "androidx.appcompat:appcompat-resources:1.3.1"
-    implementation "androidx.fragment:fragment-ktx:1.3.6"
-    implementation "androidx.annotation:annotation:1.2.0"
+    implementation "androidx.core:core-ktx:1.7.0"
+    implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
+    implementation "androidx.activity:activity-ktx:1.4.0"
+    implementation "androidx.appcompat:appcompat:1.4.0-rc01"
+    implementation "androidx.fragment:fragment-ktx:1.4.0-rc01"
+    implementation "androidx.annotation:annotation:1.3.0"
 
     implementation "androidx.preference:preference-ktx:1.1.1"
     implementation "androidx.recyclerview:recyclerview:1.2.1"
@@ -193,7 +202,7 @@ dependencies {
     implementation "androidx.work:work-runtime-ktx:$work_manager"
     playImplementation "androidx.work:work-gcm:$work_manager"
 
-    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
+    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
 
     implementation "androidx.room:room-runtime:$room"
     implementation "androidx.room:room-ktx:$room"
@@ -207,40 +216,41 @@ dependencies {
     implementation 'com.github.ncapdevi:FragNav:3.3.0'
     implementation "com.github.YarikSOffice:lingver:1.3.0"
 
-    implementation "com.squareup.moshi:moshi:$moshi"
-    implementation "com.squareup.moshi:moshi-adapters:$moshi"
-    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
+    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+    implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
+    implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
 
     implementation "com.jakewharton.timber:timber:5.0.1"
     implementation "at.favre.lib:slf4j-timber:1.0.1"
-    implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
+    implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
     implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
-    implementation "io.coil-kt:coil:1.3.2"
+    implementation "io.coil-kt:coil:1.4.0"
     implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
     implementation 'me.xdrop:fuzzywuzzy:1.3.1'
     implementation 'com.fredporciuncula:flow-preferences:1.5.0'
 
-    playImplementation platform('com.google.firebase:firebase-bom:28.4.1')
+    playImplementation platform('com.google.firebase:firebase-bom:29.0.0')
     playImplementation 'com.google.firebase:firebase-analytics-ktx'
     playImplementation 'com.google.firebase:firebase-messaging:'
     playImplementation 'com.google.firebase:firebase-crashlytics:'
     playImplementation 'com.google.android.play:core:1.10.2'
     playImplementation 'com.google.android.play:core-ktx:1.8.1'
+    playImplementation 'com.google.android.gms:play-services-ads:20.4.0'
 
-    hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
-    hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
+    hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303'
+    hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300'
 
     releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
 
     debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
-    debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6'
+    debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
 
     testImplementation "junit:junit:4.13.2"
     testImplementation "io.mockk:mockk:$mockk"
-    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
+    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
     testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
 
-    testImplementation 'org.robolectric:robolectric:4.6.1'
+    testImplementation 'org.robolectric:robolectric:4.7'
     testImplementation "androidx.test:runner:1.4.0"
     testImplementation "androidx.test.ext:junit:1.1.3"
     testImplementation "androidx.test:core:1.4.0"
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json
new file mode 100644
index 00000000..9d008060
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json
@@ -0,0 +1,2322 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 41,
+    "identityHash": "d9ce44a78495a358606612bd91603c0f",
+    "entities": [
+      {
+        "tableName": "Students",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "scrapperBaseUrl",
+            "columnName": "scrapper_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mobileBaseUrl",
+            "columnName": "mobile_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginType",
+            "columnName": "login_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginMode",
+            "columnName": "login_mode",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "certificateKey",
+            "columnName": "certificate_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "privateKey",
+            "columnName": "private_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isParent",
+            "columnName": "is_parent",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "password",
+            "columnName": "password",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "symbol",
+            "columnName": "symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "user_login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userName",
+            "columnName": "user_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentName",
+            "columnName": "student_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolSymbol",
+            "columnName": "school_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolShortName",
+            "columnName": "school_short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolName",
+            "columnName": "school_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "className",
+            "columnName": "class_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isCurrent",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registrationDate",
+            "columnName": "registration_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "nick",
+            "columnName": "nick",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "avatarColor",
+            "columnName": "avatar_color",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Students_email_symbol_student_id_school_id_class_id",
+            "unique": true,
+            "columnNames": [
+              "email",
+              "symbol",
+              "student_id",
+              "school_id",
+              "class_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Semesters",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryName",
+            "columnName": "diary_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolYear",
+            "columnName": "school_year",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterName",
+            "columnName": "semester_name",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "current",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Semesters_student_id_diary_id_semester_id",
+            "unique": true,
+            "columnNames": [
+              "student_id",
+              "diary_id",
+              "semester_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Exams",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Timetable",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectOld",
+            "columnName": "subjectOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "room",
+            "columnName": "room",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roomOld",
+            "columnName": "roomOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherOld",
+            "columnName": "teacherOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "info",
+            "columnName": "info",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isStudentPlan",
+            "columnName": "student_plan",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "changes",
+            "columnName": "changes",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "canceled",
+            "columnName": "canceled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Attendance",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "timeId",
+            "columnName": "time_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excused",
+            "columnName": "excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deleted",
+            "columnName": "deleted",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excusable",
+            "columnName": "excusable",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excuseStatus",
+            "columnName": "excuse_status",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "AttendanceSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectId",
+            "columnName": "subject_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "month",
+            "columnName": "month",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceExcused",
+            "columnName": "absence_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceForSchoolReasons",
+            "columnName": "absence_for_school_reasons",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "latenessExcused",
+            "columnName": "lateness_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Grades",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entry",
+            "columnName": "entry",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "modifier",
+            "columnName": "modifier",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "color",
+            "columnName": "color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gradeSymbol",
+            "columnName": "grade_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weight",
+            "columnName": "weight",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weightValue",
+            "columnName": "weightValue",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGrade",
+            "columnName": "predicted_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGrade",
+            "columnName": "final_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "proposedPoints",
+            "columnName": "proposed_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalPoints",
+            "columnName": "final_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointsSum",
+            "columnName": "points_sum",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "average",
+            "columnName": "average",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPredictedGradeNotified",
+            "columnName": "is_predicted_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isFinalGradeNotified",
+            "columnName": "is_final_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGradeLastChange",
+            "columnName": "predicted_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGradeLastChange",
+            "columnName": "final_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradePartialStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAverage",
+            "columnName": "class_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAverage",
+            "columnName": "student_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAmounts",
+            "columnName": "class_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAmounts",
+            "columnName": "student_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesPointsStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "others",
+            "columnName": "others",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "student",
+            "columnName": "student",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradeSemesterStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "amounts",
+            "columnName": "amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentGrade",
+            "columnName": "student_grade",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Messages",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sender",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "recipient",
+            "columnName": "recipient_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "folderId",
+            "columnName": "folder_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unread",
+            "columnName": "unread",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "removed",
+            "columnName": "removed",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasAttachments",
+            "columnName": "has_attachments",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unreadBy",
+            "columnName": "unread_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "readBy",
+            "columnName": "read_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MessageAttachments",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))",
+        "fields": [
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "oneDriveId",
+            "columnName": "one_drive_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "filename",
+            "columnName": "filename",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "real_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notes",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "category",
+            "columnName": "category",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "categoryType",
+            "columnName": "category_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPointsShow",
+            "columnName": "is_points_show",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "points",
+            "columnName": "points",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Homework",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "attachments",
+            "columnName": "attachments",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isDone",
+            "columnName": "is_done",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isAddedByUser",
+            "columnName": "is_added_by_user",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Subjects",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "LuckyNumbers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "luckyNumber",
+            "columnName": "lucky_number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "CompletedLesson",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "topic",
+            "columnName": "topic",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "substitution",
+            "columnName": "substitution",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "resources",
+            "columnName": "resources",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "ReportingUnits",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderName",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roles",
+            "columnName": "roles",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Recipients",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realName",
+            "columnName": "real_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginId",
+            "columnName": "login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "role",
+            "columnName": "role",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hash",
+            "columnName": "hash",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MobileDevices",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deviceId",
+            "columnName": "device_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Teachers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "School",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "contact",
+            "columnName": "contact",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "headmaster",
+            "columnName": "headmaster",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pedagogue",
+            "columnName": "pedagogue",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Conferences",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "agenda",
+            "columnName": "agenda",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presentOnConference",
+            "columnName": "present_on_conference",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "conferenceId",
+            "columnName": "conference_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableAdditional",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "StudentInfo",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "fullName",
+            "columnName": "full_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstName",
+            "columnName": "first_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "secondName",
+            "columnName": "second_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "surname",
+            "columnName": "surname",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthDate",
+            "columnName": "birth_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthPlace",
+            "columnName": "birth_place",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gender",
+            "columnName": "gender",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasPolishCitizenship",
+            "columnName": "has_polish_citizenship",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "familyName",
+            "columnName": "family_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "parentsNames",
+            "columnName": "parents_names",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registeredAddress",
+            "columnName": "registered_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "correspondenceAddress",
+            "columnName": "correspondence_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "phoneNumber",
+            "columnName": "phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cellPhoneNumber",
+            "columnName": "cell_phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstGuardian.fullName",
+            "columnName": "first_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.kinship",
+            "columnName": "first_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.address",
+            "columnName": "first_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.phones",
+            "columnName": "first_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.email",
+            "columnName": "first_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.fullName",
+            "columnName": "second_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.kinship",
+            "columnName": "second_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.address",
+            "columnName": "second_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.phones",
+            "columnName": "second_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.email",
+            "columnName": "second_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableHeaders",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "SchoolAnnouncements",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notifications",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "data",
+            "columnName": "data",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9ce44a78495a358606612bd91603c0f')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
new file mode 100644
index 00000000..a5faa57b
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
@@ -0,0 +1,2396 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 42,
+    "identityHash": "5c8b7f9409294ecdebf9f74a44f8e883",
+    "entities": [
+      {
+        "tableName": "Students",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "scrapperBaseUrl",
+            "columnName": "scrapper_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mobileBaseUrl",
+            "columnName": "mobile_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginType",
+            "columnName": "login_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginMode",
+            "columnName": "login_mode",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "certificateKey",
+            "columnName": "certificate_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "privateKey",
+            "columnName": "private_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isParent",
+            "columnName": "is_parent",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "password",
+            "columnName": "password",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "symbol",
+            "columnName": "symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "user_login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userName",
+            "columnName": "user_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentName",
+            "columnName": "student_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolSymbol",
+            "columnName": "school_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolShortName",
+            "columnName": "school_short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolName",
+            "columnName": "school_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "className",
+            "columnName": "class_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isCurrent",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registrationDate",
+            "columnName": "registration_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "nick",
+            "columnName": "nick",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "avatarColor",
+            "columnName": "avatar_color",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Students_email_symbol_student_id_school_id_class_id",
+            "unique": true,
+            "columnNames": [
+              "email",
+              "symbol",
+              "student_id",
+              "school_id",
+              "class_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Semesters",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryName",
+            "columnName": "diary_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolYear",
+            "columnName": "school_year",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterName",
+            "columnName": "semester_name",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "current",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Semesters_student_id_diary_id_semester_id",
+            "unique": true,
+            "columnNames": [
+              "student_id",
+              "diary_id",
+              "semester_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Exams",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Timetable",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectOld",
+            "columnName": "subjectOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "room",
+            "columnName": "room",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roomOld",
+            "columnName": "roomOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherOld",
+            "columnName": "teacherOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "info",
+            "columnName": "info",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isStudentPlan",
+            "columnName": "student_plan",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "changes",
+            "columnName": "changes",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "canceled",
+            "columnName": "canceled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Attendance",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "timeId",
+            "columnName": "time_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excused",
+            "columnName": "excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deleted",
+            "columnName": "deleted",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excusable",
+            "columnName": "excusable",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excuseStatus",
+            "columnName": "excuse_status",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "AttendanceSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectId",
+            "columnName": "subject_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "month",
+            "columnName": "month",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceExcused",
+            "columnName": "absence_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceForSchoolReasons",
+            "columnName": "absence_for_school_reasons",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "latenessExcused",
+            "columnName": "lateness_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Grades",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entry",
+            "columnName": "entry",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "modifier",
+            "columnName": "modifier",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "color",
+            "columnName": "color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gradeSymbol",
+            "columnName": "grade_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weight",
+            "columnName": "weight",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weightValue",
+            "columnName": "weightValue",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGrade",
+            "columnName": "predicted_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGrade",
+            "columnName": "final_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "proposedPoints",
+            "columnName": "proposed_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalPoints",
+            "columnName": "final_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointsSum",
+            "columnName": "points_sum",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "average",
+            "columnName": "average",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPredictedGradeNotified",
+            "columnName": "is_predicted_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isFinalGradeNotified",
+            "columnName": "is_final_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGradeLastChange",
+            "columnName": "predicted_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGradeLastChange",
+            "columnName": "final_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradePartialStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAverage",
+            "columnName": "class_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAverage",
+            "columnName": "student_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAmounts",
+            "columnName": "class_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAmounts",
+            "columnName": "student_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesPointsStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "others",
+            "columnName": "others",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "student",
+            "columnName": "student",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradeSemesterStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "amounts",
+            "columnName": "amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentGrade",
+            "columnName": "student_grade",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Messages",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sender",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "recipient",
+            "columnName": "recipient_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "folderId",
+            "columnName": "folder_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unread",
+            "columnName": "unread",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "removed",
+            "columnName": "removed",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasAttachments",
+            "columnName": "has_attachments",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unreadBy",
+            "columnName": "unread_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "readBy",
+            "columnName": "read_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MessageAttachments",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))",
+        "fields": [
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "oneDriveId",
+            "columnName": "one_drive_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "filename",
+            "columnName": "filename",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "real_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notes",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "category",
+            "columnName": "category",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "categoryType",
+            "columnName": "category_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPointsShow",
+            "columnName": "is_points_show",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "points",
+            "columnName": "points",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Homework",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "attachments",
+            "columnName": "attachments",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isDone",
+            "columnName": "is_done",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isAddedByUser",
+            "columnName": "is_added_by_user",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Subjects",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "LuckyNumbers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "luckyNumber",
+            "columnName": "lucky_number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "CompletedLesson",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "topic",
+            "columnName": "topic",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "substitution",
+            "columnName": "substitution",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "resources",
+            "columnName": "resources",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "ReportingUnits",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderName",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roles",
+            "columnName": "roles",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Recipients",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realName",
+            "columnName": "real_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginId",
+            "columnName": "login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "role",
+            "columnName": "role",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hash",
+            "columnName": "hash",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MobileDevices",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deviceId",
+            "columnName": "device_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Teachers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "School",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "contact",
+            "columnName": "contact",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "headmaster",
+            "columnName": "headmaster",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pedagogue",
+            "columnName": "pedagogue",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Conferences",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "agenda",
+            "columnName": "agenda",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presentOnConference",
+            "columnName": "present_on_conference",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "conferenceId",
+            "columnName": "conference_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableAdditional",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "StudentInfo",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "fullName",
+            "columnName": "full_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstName",
+            "columnName": "first_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "secondName",
+            "columnName": "second_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "surname",
+            "columnName": "surname",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthDate",
+            "columnName": "birth_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthPlace",
+            "columnName": "birth_place",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gender",
+            "columnName": "gender",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasPolishCitizenship",
+            "columnName": "has_polish_citizenship",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "familyName",
+            "columnName": "family_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "parentsNames",
+            "columnName": "parents_names",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registeredAddress",
+            "columnName": "registered_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "correspondenceAddress",
+            "columnName": "correspondence_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "phoneNumber",
+            "columnName": "phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cellPhoneNumber",
+            "columnName": "cell_phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstGuardian.fullName",
+            "columnName": "first_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.kinship",
+            "columnName": "first_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.address",
+            "columnName": "first_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.phones",
+            "columnName": "first_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.email",
+            "columnName": "first_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.fullName",
+            "columnName": "second_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.kinship",
+            "columnName": "second_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.address",
+            "columnName": "second_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.phones",
+            "columnName": "second_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.email",
+            "columnName": "second_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableHeaders",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "SchoolAnnouncements",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notifications",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "data",
+            "columnName": "data",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "AdminMessages",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "versionMin",
+            "columnName": "version_name",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "versionMax",
+            "columnName": "version_max",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "targetRegisterHost",
+            "columnName": "target_register_host",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "targetFlavor",
+            "columnName": "target_flavor",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "destinationUrl",
+            "columnName": "destination_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "priority",
+            "columnName": "priority",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5c8b7f9409294ecdebf9f74a44f8e883')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json
new file mode 100644
index 00000000..22c0d812
--- /dev/null
+++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json
@@ -0,0 +1,2408 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 43,
+    "identityHash": "66946510bb620ae82686a5a1a31aba18",
+    "entities": [
+      {
+        "tableName": "Students",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "scrapperBaseUrl",
+            "columnName": "scrapper_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mobileBaseUrl",
+            "columnName": "mobile_base_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginType",
+            "columnName": "login_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginMode",
+            "columnName": "login_mode",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "certificateKey",
+            "columnName": "certificate_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "privateKey",
+            "columnName": "private_key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isParent",
+            "columnName": "is_parent",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "password",
+            "columnName": "password",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "symbol",
+            "columnName": "symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "user_login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userName",
+            "columnName": "user_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentName",
+            "columnName": "student_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolSymbol",
+            "columnName": "school_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolShortName",
+            "columnName": "school_short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolName",
+            "columnName": "school_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "className",
+            "columnName": "class_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isCurrent",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registrationDate",
+            "columnName": "registration_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "nick",
+            "columnName": "nick",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "avatarColor",
+            "columnName": "avatar_color",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Students_email_symbol_student_id_school_id_class_id",
+            "unique": true,
+            "columnNames": [
+              "email",
+              "symbol",
+              "student_id",
+              "school_id",
+              "class_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Semesters",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryName",
+            "columnName": "diary_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "schoolYear",
+            "columnName": "school_year",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterName",
+            "columnName": "semester_name",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "current",
+            "columnName": "is_current",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_Semesters_student_id_diary_id_semester_id",
+            "unique": true,
+            "columnNames": [
+              "student_id",
+              "diary_id",
+              "semester_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Exams",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Timetable",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectOld",
+            "columnName": "subjectOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "group",
+            "columnName": "group",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "room",
+            "columnName": "room",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roomOld",
+            "columnName": "roomOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherOld",
+            "columnName": "teacherOld",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "info",
+            "columnName": "info",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isStudentPlan",
+            "columnName": "student_plan",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "changes",
+            "columnName": "changes",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "canceled",
+            "columnName": "canceled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Attendance",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "timeId",
+            "columnName": "time_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excused",
+            "columnName": "excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deleted",
+            "columnName": "deleted",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excusable",
+            "columnName": "excusable",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "excuseStatus",
+            "columnName": "excuse_status",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "AttendanceSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subjectId",
+            "columnName": "subject_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "month",
+            "columnName": "month",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presence",
+            "columnName": "presence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceExcused",
+            "columnName": "absence_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absenceForSchoolReasons",
+            "columnName": "absence_for_school_reasons",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lateness",
+            "columnName": "lateness",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "latenessExcused",
+            "columnName": "lateness_excused",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exemption",
+            "columnName": "exemption",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Grades",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entry",
+            "columnName": "entry",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "modifier",
+            "columnName": "modifier",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "color",
+            "columnName": "color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gradeSymbol",
+            "columnName": "grade_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weight",
+            "columnName": "weight",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "weightValue",
+            "columnName": "weightValue",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesSummary",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGrade",
+            "columnName": "predicted_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGrade",
+            "columnName": "final_grade",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "proposedPoints",
+            "columnName": "proposed_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalPoints",
+            "columnName": "final_points",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointsSum",
+            "columnName": "points_sum",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "average",
+            "columnName": "average",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPredictedGradeNotified",
+            "columnName": "is_predicted_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isFinalGradeNotified",
+            "columnName": "is_final_grade_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "predictedGradeLastChange",
+            "columnName": "predicted_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "finalGradeLastChange",
+            "columnName": "final_grade_last_change",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradePartialStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAverage",
+            "columnName": "class_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAverage",
+            "columnName": "student_average",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classAmounts",
+            "columnName": "class_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentAmounts",
+            "columnName": "student_amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradesPointsStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "others",
+            "columnName": "others",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "student",
+            "columnName": "student",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "GradeSemesterStatistics",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "amounts",
+            "columnName": "amounts",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentGrade",
+            "columnName": "student_grade",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Messages",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sender",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "recipient",
+            "columnName": "recipient_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "folderId",
+            "columnName": "folder_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unread",
+            "columnName": "unread",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "removed",
+            "columnName": "removed",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasAttachments",
+            "columnName": "has_attachments",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unreadBy",
+            "columnName": "unread_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "readBy",
+            "columnName": "read_by",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MessageAttachments",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))",
+        "fields": [
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "messageId",
+            "columnName": "message_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "oneDriveId",
+            "columnName": "one_drive_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "filename",
+            "columnName": "filename",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "real_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notes",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "category",
+            "columnName": "category",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "categoryType",
+            "columnName": "category_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isPointsShow",
+            "columnName": "is_points_show",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "points",
+            "columnName": "points",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isRead",
+            "columnName": "is_read",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Homework",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "semesterId",
+            "columnName": "semester_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entryDate",
+            "columnName": "entry_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "attachments",
+            "columnName": "attachments",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isDone",
+            "columnName": "is_done",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isAddedByUser",
+            "columnName": "is_added_by_user",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Subjects",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "LuckyNumbers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "luckyNumber",
+            "columnName": "lucky_number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "CompletedLesson",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "topic",
+            "columnName": "topic",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacher",
+            "columnName": "teacher",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "teacherSymbol",
+            "columnName": "teacher_symbol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "substitution",
+            "columnName": "substitution",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "absence",
+            "columnName": "absence",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "resources",
+            "columnName": "resources",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "ReportingUnits",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "real_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderId",
+            "columnName": "sender_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "senderName",
+            "columnName": "sender_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "roles",
+            "columnName": "roles",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Recipients",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realId",
+            "columnName": "real_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "realName",
+            "columnName": "real_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "loginId",
+            "columnName": "login_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "unitId",
+            "columnName": "unit_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "role",
+            "columnName": "role",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hash",
+            "columnName": "hash",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "MobileDevices",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "userLoginId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "deviceId",
+            "columnName": "device_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Teachers",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "short_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "School",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "classId",
+            "columnName": "class_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "contact",
+            "columnName": "contact",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "headmaster",
+            "columnName": "headmaster",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pedagogue",
+            "columnName": "pedagogue",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Conferences",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "agenda",
+            "columnName": "agenda",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "presentOnConference",
+            "columnName": "present_on_conference",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "conferenceId",
+            "columnName": "conference_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableAdditional",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "start",
+            "columnName": "start",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "end",
+            "columnName": "end",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "StudentInfo",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "fullName",
+            "columnName": "full_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstName",
+            "columnName": "first_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "secondName",
+            "columnName": "second_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "surname",
+            "columnName": "surname",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthDate",
+            "columnName": "birth_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "birthPlace",
+            "columnName": "birth_place",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gender",
+            "columnName": "gender",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasPolishCitizenship",
+            "columnName": "has_polish_citizenship",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "familyName",
+            "columnName": "family_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "parentsNames",
+            "columnName": "parents_names",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "registeredAddress",
+            "columnName": "registered_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "correspondenceAddress",
+            "columnName": "correspondence_address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "phoneNumber",
+            "columnName": "phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "cellPhoneNumber",
+            "columnName": "cell_phone_number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstGuardian.fullName",
+            "columnName": "first_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.kinship",
+            "columnName": "first_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.address",
+            "columnName": "first_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.phones",
+            "columnName": "first_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "firstGuardian.email",
+            "columnName": "first_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.fullName",
+            "columnName": "second_guardian_full_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.kinship",
+            "columnName": "second_guardian_kinship",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.address",
+            "columnName": "second_guardian_address",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.phones",
+            "columnName": "second_guardian_phones",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "secondGuardian.email",
+            "columnName": "second_guardian_email",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "TimetableHeaders",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "diaryId",
+            "columnName": "diary_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "SchoolAnnouncements",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subject",
+            "columnName": "subject",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isNotified",
+            "columnName": "is_notified",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Notifications",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "studentId",
+            "columnName": "student_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "data",
+            "columnName": "data",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "AdminMessages",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "versionMin",
+            "columnName": "version_name",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "versionMax",
+            "columnName": "version_max",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "targetRegisterHost",
+            "columnName": "target_register_host",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "targetFlavor",
+            "columnName": "target_flavor",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "destinationUrl",
+            "columnName": "destination_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "priority",
+            "columnName": "priority",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '66946510bb620ae82686a5a1a31aba18')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84c50523..5928c23a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,10 +38,10 @@
         android:allowBackup="false"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="false"
         android:theme="@style/WulkanowyTheme"
-        android:usesCleartextTraffic="true"
         tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
         <activity
             android:name=".ui.modules.splash.SplashActivity"
@@ -106,7 +106,8 @@
         </service>
         <service
             android:name=".services.messaging.AppMessagingService"
-            android:exported="false">
+            android:exported="false"
+            tools:ignore="MissingClass">
             <intent-filter>
                 <action android:name="com.google.firebase.MESSAGING_EVENT" />
             </intent-filter>
@@ -152,44 +153,44 @@
                 android:resource="@xml/provider_paths" />
         </provider>
 
-        <meta-data
-            android:name="install_channel"
-            android:value="${install_channel}" />
-
         <!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
         <!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
         <provider
             android:name="com.google.firebase.provider.FirebaseInitProvider"
             android:authorities="${applicationId}.firebaseinitprovider"
             android:enabled="${firebase_enabled}"
-            android:exported="false" />
+            android:exported="false"
+            tools:ignore="MissingClass" />
 
+        <meta-data
+            android:name="install_channel"
+            android:value="${install_channel}" />
         <meta-data
             android:name="firebase_analytics_collection_enabled"
             android:value="${firebase_enabled}" />
-
         <meta-data
             android:name="google_analytics_adid_collection_enabled"
             android:value="${firebase_enabled}" />
-
         <meta-data
             android:name="firebase_crashlytics_collection_enabled"
             android:value="${firebase_enabled}" />
-
         <meta-data
             android:name="firebase_messaging_auto_init_enabled"
             android:value="${firebase_enabled}" />
-
         <meta-data
             android:name="firebase_inapp_messaging_auto_data_collection_enabled"
             android:value="${firebase_enabled}" />
-
         <meta-data
             android:name="com.google.firebase.messaging.default_notification_icon"
             android:resource="@drawable/ic_stat_all" />
-
         <meta-data
             android:name="com.google.firebase.messaging.default_notification_channel_id"
             android:value="push_channel" />
+        <meta-data
+            android:name="com.google.android.gms.ads.APPLICATION_ID"
+            android:value="${admob_project_id}" />
+        <meta-data
+            android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
+            android:value="true" />
     </application>
 </manifest>
diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
index 4621c592..7cdeb622 100644
--- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
+++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt
@@ -1,12 +1,10 @@
 package io.github.wulkanowy
 
-import android.annotation.SuppressLint
 import android.app.Application
 import android.util.Log.DEBUG
 import android.util.Log.INFO
 import android.util.Log.VERBOSE
 import android.webkit.WebView
-import androidx.fragment.app.FragmentManager
 import androidx.hilt.work.HiltWorkerFactory
 import androidx.work.Configuration
 import com.yariksoffice.lingver.Lingver
@@ -41,10 +39,8 @@ class WulkanowyApp : Application(), Configuration.Provider {
     @Inject
     lateinit var analyticsHelper: AnalyticsHelper
 
-    @SuppressLint("UnsafeOptInUsageWarning")
     override fun onCreate() {
         super.onCreate()
-        FragmentManager.enableNewStateManager(false)
         initializeAppLanguage()
         themeManager.applyDefaultTheme()
         initLogging()
diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
similarity index 70%
rename from app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt
rename to app/src/main/java/io/github/wulkanowy/data/DataModule.kt
index f1b719e5..cac3ffc2 100644
--- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt
@@ -2,62 +2,100 @@ package io.github.wulkanowy.data
 
 import android.content.Context
 import android.content.SharedPreferences
-import android.content.res.AssetManager
-import android.content.res.Resources
 import androidx.preference.PreferenceManager
 import com.chuckerteam.chucker.api.ChuckerCollector
 import com.chuckerteam.chucker.api.ChuckerInterceptor
 import com.chuckerteam.chucker.api.RetentionManager
 import com.fredporciuncula.flow.preferences.FlowSharedPreferences
-import com.squareup.moshi.Moshi
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
 import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.components.SingletonComponent
+import io.github.wulkanowy.data.api.AdminMessageService
 import io.github.wulkanowy.data.db.AppDatabase
 import io.github.wulkanowy.data.db.SharedPrefProvider
 import io.github.wulkanowy.data.repositories.PreferencesRepository
 import io.github.wulkanowy.sdk.Sdk
 import io.github.wulkanowy.utils.AppInfo
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.create
 import timber.log.Timber
+import java.util.concurrent.TimeUnit
 import javax.inject.Singleton
 
 @Module
 @InstallIn(SingletonComponent::class)
-internal class RepositoryModule {
+internal class DataModule {
 
     @Singleton
     @Provides
-    fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
-        return Sdk().apply {
+    fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
+        Sdk().apply {
             androidVersion = android.os.Build.VERSION.RELEASE
             buildTag = android.os.Build.MODEL
             setSimpleHttpLogger { Timber.d(it) }
 
             // for debug only
-            addInterceptor(
-                ChuckerInterceptor.Builder(context)
-                    .collector(chuckerCollector)
-                    .alwaysReadResponseBody(true)
-                    .build(), network = true
-            )
+            addInterceptor(chuckerInterceptor, network = true)
         }
-    }
 
     @Singleton
     @Provides
     fun provideChuckerCollector(
         @ApplicationContext context: Context,
         prefRepository: PreferencesRepository
-    ): ChuckerCollector {
-        return ChuckerCollector(
-            context = context,
-            showNotification = prefRepository.isDebugNotificationEnable,
-            retentionPeriod = RetentionManager.Period.ONE_HOUR
-        )
-    }
+    ) = ChuckerCollector(
+        context = context,
+        showNotification = prefRepository.isDebugNotificationEnable,
+        retentionPeriod = RetentionManager.Period.ONE_HOUR
+    )
+
+    @Singleton
+    @Provides
+    fun provideChuckerInterceptor(
+        @ApplicationContext context: Context,
+        chuckerCollector: ChuckerCollector
+    ) = ChuckerInterceptor.Builder(context)
+        .collector(chuckerCollector)
+        .alwaysReadResponseBody(true)
+        .build()
+
+    @Singleton
+    @Provides
+    fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient =
+        OkHttpClient.Builder()
+            .addNetworkInterceptor(chuckerInterceptor)
+            .addInterceptor(HttpLoggingInterceptor().apply {
+                level = HttpLoggingInterceptor.Level.BASIC
+            })
+            .connectTimeout(30, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .build()
+
+    @OptIn(ExperimentalSerializationApi::class)
+    @Singleton
+    @Provides
+    fun provideRetrofit(
+        okHttpClient: OkHttpClient,
+        json: Json,
+        appInfo: AppInfo
+    ): Retrofit = Retrofit.Builder()
+        .baseUrl(appInfo.messagesBaseUrl)
+        .client(okHttpClient)
+        .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
+        .build()
+
+    @Singleton
+    @Provides
+    fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
 
     @Singleton
     @Provides
@@ -67,14 +105,6 @@ internal class RepositoryModule {
         appInfo: AppInfo
     ) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
 
-    @Singleton
-    @Provides
-    fun provideResources(@ApplicationContext context: Context): Resources = context.resources
-
-    @Singleton
-    @Provides
-    fun provideAssets(@ApplicationContext context: Context): AssetManager = context.assets
-
     @Singleton
     @Provides
     fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
@@ -88,7 +118,9 @@ internal class RepositoryModule {
 
     @Singleton
     @Provides
-    fun provideMoshi() = Moshi.Builder().build()
+    fun provideJson() = Json {
+        ignoreUnknownKeys = true
+    }
 
     @Singleton
     @Provides
@@ -206,4 +238,8 @@ internal class RepositoryModule {
     @Singleton
     @Provides
     fun provideNotificationDao(database: AppDatabase) = database.notificationDao
+
+    @Singleton
+    @Provides
+    fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt b/app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt
new file mode 100644
index 00000000..23f5af24
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/api/AdminMessageService.kt
@@ -0,0 +1,12 @@
+package io.github.wulkanowy.data.api
+
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import retrofit2.http.GET
+import javax.inject.Singleton
+
+@Singleton
+interface AdminMessageService {
+
+    @GET("/v1.json")
+    suspend fun getAdminMessages(): List<AdminMessage>
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
index 09aa972f..7e6d609f 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt
@@ -6,6 +6,7 @@ import androidx.room.Room
 import androidx.room.RoomDatabase
 import androidx.room.RoomDatabase.JournalMode.TRUNCATE
 import androidx.room.TypeConverters
+import io.github.wulkanowy.data.db.dao.AdminMessageDao
 import io.github.wulkanowy.data.db.dao.AttendanceDao
 import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
 import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
@@ -35,6 +36,7 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
 import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
 import io.github.wulkanowy.data.db.dao.TimetableDao
 import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
+import io.github.wulkanowy.data.db.entities.AdminMessage
 import io.github.wulkanowy.data.db.entities.Attendance
 import io.github.wulkanowy.data.db.entities.AttendanceSummary
 import io.github.wulkanowy.data.db.entities.CompletedLesson
@@ -98,6 +100,9 @@ import io.github.wulkanowy.data.db.migrations.Migration38
 import io.github.wulkanowy.data.db.migrations.Migration39
 import io.github.wulkanowy.data.db.migrations.Migration4
 import io.github.wulkanowy.data.db.migrations.Migration40
+import io.github.wulkanowy.data.db.migrations.Migration41
+import io.github.wulkanowy.data.db.migrations.Migration42
+import io.github.wulkanowy.data.db.migrations.Migration43
 import io.github.wulkanowy.data.db.migrations.Migration5
 import io.github.wulkanowy.data.db.migrations.Migration6
 import io.github.wulkanowy.data.db.migrations.Migration7
@@ -137,7 +142,8 @@ import javax.inject.Singleton
         StudentInfo::class,
         TimetableHeader::class,
         SchoolAnnouncement::class,
-        Notification::class
+        Notification::class,
+        AdminMessage::class
     ],
     version = AppDatabase.VERSION_SCHEMA,
     exportSchema = true
@@ -146,7 +152,7 @@ import javax.inject.Singleton
 abstract class AppDatabase : RoomDatabase() {
 
     companion object {
-        const val VERSION_SCHEMA = 40
+        const val VERSION_SCHEMA = 43
 
         fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
             Migration2(),
@@ -187,7 +193,10 @@ abstract class AppDatabase : RoomDatabase() {
             Migration37(),
             Migration38(),
             Migration39(),
-            Migration40()
+            Migration40(),
+            Migration41(sharedPrefProvider),
+            Migration42(),
+            Migration43()
         )
 
         fun newInstance(
@@ -259,4 +268,6 @@ abstract class AppDatabase : RoomDatabase() {
     abstract val schoolAnnouncementDao: SchoolAnnouncementDao
 
     abstract val notificationDao: NotificationDao
+
+    abstract val adminMessagesDao: AdminMessageDao
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt
index def0b371..1993c433 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt
@@ -1,9 +1,10 @@
 package io.github.wulkanowy.data.db
 
 import androidx.room.TypeConverter
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.Types
-import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
@@ -13,15 +14,7 @@ import java.util.Date
 
 class Converters {
 
-    private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
-
-    private val integerListAdapter by lazy {
-        moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
-    }
-
-    private val stringListPairAdapter by lazy {
-        moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
-    }
+    private val json = Json
 
     @TypeConverter
     fun timestampToDate(value: Long?): LocalDate? = value?.run {
@@ -51,21 +44,25 @@ class Converters {
 
     @TypeConverter
     fun intListToJson(list: List<Int>): String {
-        return integerListAdapter.toJson(list)
+        return json.encodeToString(list)
     }
 
     @TypeConverter
     fun jsonToIntList(value: String): List<Int> {
-        return integerListAdapter.fromJson(value).orEmpty()
+        return json.decodeFromString(value)
     }
 
     @TypeConverter
     fun stringPairListToJson(list: List<Pair<String, String>>): String {
-        return stringListPairAdapter.toJson(list)
+        return json.encodeToString(list)
     }
 
     @TypeConverter
     fun jsonToStringPairList(value: String): List<Pair<String, String>> {
-        return stringListPairAdapter.fromJson(value).orEmpty()
+        return try {
+            json.decodeFromString(value)
+        } catch (e: SerializationException) {
+            emptyList() // handle errors from old gson Pair serialized data
+        }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt
index 0623d403..4929f046 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/SharedPrefProvider.kt
@@ -22,11 +22,14 @@ class SharedPrefProvider @Inject constructor(
 
     fun getString(key: String) = sharedPref.getString(key, null)
 
-    fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
+    fun getString(key: String, defaultValue: String): String =
+        sharedPref.getString(key, defaultValue) ?: defaultValue
 
-    fun getBoolean(key: String, defaultValue: Boolean): Boolean = sharedPref.getBoolean(key, defaultValue)
+    fun getBoolean(key: String, defaultValue: Boolean): Boolean =
+        sharedPref.getBoolean(key, defaultValue)
 
-    fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = sharedPref.edit(sync) { putBoolean(key, value) }
+    fun putBoolean(key: String, value: Boolean, sync: Boolean = false) =
+        sharedPref.edit(sync) { putBoolean(key, value) }
 
     fun putString(key: String, value: String?, sync: Boolean = false) {
         sharedPref.edit(sync) { putString(key, value) }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/adapters/PairAdapterFactory.kt b/app/src/main/java/io/github/wulkanowy/data/db/adapters/PairAdapterFactory.kt
deleted file mode 100644
index 4a9b168d..00000000
--- a/app/src/main/java/io/github/wulkanowy/data/db/adapters/PairAdapterFactory.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package io.github.wulkanowy.data.db.adapters
-
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.JsonReader
-import com.squareup.moshi.JsonWriter
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.Types
-import java.lang.reflect.ParameterizedType
-import java.lang.reflect.Type
-
-object PairAdapterFactory : JsonAdapter.Factory {
-
-    override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
-        if (type !is ParameterizedType || List::class.java != type.rawType) return null
-        if (type.actualTypeArguments[0] != Pair::class.java) return null
-
-        val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
-        val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
-
-        val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
-        val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
-
-        return PairAdapter(listAdapter, mapAdapter)
-    }
-
-    private class PairAdapter(
-        private val listAdapter: JsonAdapter<List<Map<String, String>>>,
-        private val mapAdapter: JsonAdapter<Map<String, String>>,
-    ) : JsonAdapter<List<Pair<String, String>>>() {
-
-        override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
-            writer.beginArray()
-            value?.forEach {
-                writer.beginObject()
-                writer.name("first").value(it.first)
-                writer.name("second").value(it.second)
-                writer.endObject()
-            }
-            writer.endArray()
-        }
-
-        override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
-            return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
-            else deserializeGsonPair(reader)
-        }
-
-        // for compatibility with 0.21.0
-        private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
-            val map = mapAdapter.fromJson(reader) ?: return null
-
-            return map.entries.map {
-                it.key to it.value
-            }
-        }
-
-        private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
-            val list = listAdapter.fromJson(reader) ?: return null
-
-            return list.map {
-                require(it.size == 2) {
-                    "pair with more or less than two elements: $list"
-                }
-
-                it["first"].orEmpty() to it["second"].orEmpty()
-            }
-        }
-    }
-}
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt
new file mode 100644
index 00000000..87f4812d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AdminMessageDao.kt
@@ -0,0 +1,25 @@
+package io.github.wulkanowy.data.db.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import androidx.room.Transaction
+import io.github.wulkanowy.data.db.entities.AdminMessage
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Singleton
+
+@Singleton
+@Dao
+abstract class AdminMessageDao : BaseDao<AdminMessage> {
+
+    @Query("SELECT * FROM AdminMessages")
+    abstract fun loadAll(): Flow<List<AdminMessage>>
+
+    @Transaction
+    open suspend fun removeOldAndSaveNew(
+        oldMessages: List<AdminMessage>,
+        newMessages: List<AdminMessage>
+    ) {
+        deleteAll(oldMessages)
+        insertAll(newMessages)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt
index 8ef3fd44..c6c255a1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt
@@ -11,6 +11,11 @@ import javax.inject.Singleton
 @Dao
 interface AttendanceDao : BaseDao<Attendance> {
 
-    @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
-    fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Attendance>>
+    @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
+    fun loadAll(
+        diaryId: Int,
+        studentId: Int,
+        start: LocalDate,
+        end: LocalDate
+    ): Flow<List<Attendance>>
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
new file mode 100644
index 00000000..b7b0cf58
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AdminMessage.kt
@@ -0,0 +1,37 @@
+package io.github.wulkanowy.data.db.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import kotlinx.serialization.Serializable
+
+@Serializable
+@Entity(tableName = "AdminMessages")
+data class AdminMessage(
+
+    @PrimaryKey
+    val id: Int,
+
+    val title: String,
+
+    val content: String,
+
+    @ColumnInfo(name = "version_name")
+    val versionMin: Int? = null,
+
+    @ColumnInfo(name = "version_max")
+    val versionMax: Int? = null,
+
+    @ColumnInfo(name = "target_register_host")
+    val targetRegisterHost: String? = null,
+
+    @ColumnInfo(name = "target_flavor")
+    val targetFlavor: String? = null,
+
+    @ColumnInfo(name = "destination_url")
+    val destinationUrl: String? = null,
+
+    val priority: String,
+
+    val type: String
+)
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt
index f141d5d5..b40dd52e 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt
@@ -47,4 +47,7 @@ data class Attendance(
 
     @PrimaryKey(autoGenerate = true)
     var id: Long = 0
+
+    @ColumnInfo(name = "is_notified")
+    var isNotified: Boolean = true
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt
index 04ee1e8c..4538cf31 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Homework.kt
@@ -40,4 +40,7 @@ data class Homework(
 
     @ColumnInfo(name = "is_notified")
     var isNotified: Boolean = true
+
+    @ColumnInfo(name = "is_added_by_user")
+    var isAddedByUser: Boolean = false
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt
index 60e67d32..22332270 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Recipient.kt
@@ -3,10 +3,9 @@ package io.github.wulkanowy.data.db.entities
 import androidx.room.ColumnInfo
 import androidx.room.Entity
 import androidx.room.PrimaryKey
-import com.squareup.moshi.JsonClass
 import java.io.Serializable
 
-@JsonClass(generateAdapter = true)
+@kotlinx.serialization.Serializable
 @Entity(tableName = "Recipients")
 data class Recipient(
 
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt
index 1bf159ef..29b3737b 100644
--- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt
@@ -50,4 +50,7 @@ data class Timetable(
 
     @PrimaryKey(autoGenerate = true)
     var id: Long = 0
+
+    @ColumnInfo(name = "is_notified")
+    var isNotified: Boolean = true
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt
new file mode 100644
index 00000000..0080e057
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt
@@ -0,0 +1,21 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import io.github.wulkanowy.data.db.SharedPrefProvider
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
+
+class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {
+
+    override fun migrate(database: SupportSQLiteDatabase) {
+        migrateSharedPreferences()
+        database.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0")
+    }
+
+    private fun migrateSharedPreferences() {
+        if (sharedPrefProvider.getBoolean("pref_key_expand_grade", false)) {
+            sharedPrefProvider.putString("pref_key_expand_grade_mode", GradeExpandMode.ALWAYS_EXPANDED.value)
+        }
+        sharedPrefProvider.delete("pref_key_expand_grade")
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt
new file mode 100644
index 00000000..3d66f301
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt
@@ -0,0 +1,24 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration42 : Migration(41, 42) {
+
+    override fun migrate(database: SupportSQLiteDatabase) {
+        database.execSQL(
+            """CREATE TABLE IF NOT EXISTS `AdminMessages` (
+            `id` INTEGER NOT NULL, 
+            `title` TEXT NOT NULL, 
+            `content` TEXT NOT NULL, 
+            `version_name` INTEGER, 
+            `version_max` INTEGER, 
+            `target_register_host` TEXT, 
+            `target_flavor` TEXT,
+            `destination_url` TEXT,
+            `priority` TEXT NOT NULL,
+            `type` TEXT NOT NULL, 
+            PRIMARY KEY(`id`))"""
+        )
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt
new file mode 100644
index 00000000..68c2834d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt
@@ -0,0 +1,12 @@
+package io.github.wulkanowy.data.db.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration43 : Migration(42, 43) {
+
+    override fun migrate(database: SupportSQLiteDatabase) {
+        database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+        database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt
index d2338c28..4165b3f1 100644
--- a/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/Contributor.kt
@@ -1,8 +1,8 @@
 package io.github.wulkanowy.data.pojos
 
-import com.squareup.moshi.JsonClass
+import kotlinx.serialization.Serializable
 
-@JsonClass(generateAdapter = true)
+@Serializable
 class Contributor(
     val displayName: String,
     val githubUsername: String
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt
index a79b70cd..2e568e37 100644
--- a/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/MessageDraft.kt
@@ -1,9 +1,9 @@
 package io.github.wulkanowy.data.pojos
 
-import com.squareup.moshi.JsonClass
 import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
+import kotlinx.serialization.Serializable
 
-@JsonClass(generateAdapter = true)
+@Serializable
 data class MessageDraft(
     val recipients: List<RecipientChipItem>,
     val subject: String,
diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt
index 0b4603ef..0748ba64 100644
--- a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt
@@ -1,36 +1,19 @@
 package io.github.wulkanowy.data.pojos
 
-import androidx.annotation.DrawableRes
-import androidx.annotation.PluralsRes
-import androidx.annotation.StringRes
+import android.content.Intent
 import io.github.wulkanowy.services.sync.notifications.NotificationType
-import io.github.wulkanowy.ui.modules.main.MainView
 
-sealed interface NotificationData {
+data class NotificationData(
+    val intentToStart: Intent,
+    val title: String,
+    val content: String
+)
+
+data class GroupNotificationData(
+    val notificationDataList: List<NotificationData>,
+    val title: String,
+    val content: String,
+    val intentToStart: Intent,
     val type: NotificationType
-    val startMenu: MainView.Section
-    val icon: Int
-    val titleStringRes: Int
-    val contentStringRes: Int
-}
+)
 
-data class MultipleNotificationsData(
-    override val type: NotificationType,
-    override val startMenu: MainView.Section,
-    @DrawableRes override val icon: Int,
-    @PluralsRes override val titleStringRes: Int,
-    @PluralsRes override val contentStringRes: Int,
-
-    @PluralsRes val summaryStringRes: Int,
-    val lines: List<String>,
-) : NotificationData
-
-data class OneNotificationData(
-    override val type: NotificationType,
-    override val startMenu: MainView.Section,
-    @DrawableRes override val icon: Int,
-    @StringRes override val titleStringRes: Int,
-    @StringRes override val contentStringRes: Int,
-
-    val contentValues: List<String>,
-) : NotificationData
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
new file mode 100644
index 00000000..1b17e3bf
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt
@@ -0,0 +1,52 @@
+package io.github.wulkanowy.data.repositories
+
+import io.github.wulkanowy.data.api.AdminMessageService
+import io.github.wulkanowy.data.db.dao.AdminMessageDao
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.utils.AppInfo
+import io.github.wulkanowy.utils.AutoRefreshHelper
+import io.github.wulkanowy.utils.networkBoundResource
+import kotlinx.coroutines.sync.Mutex
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AdminMessageRepository @Inject constructor(
+    private val adminMessageService: AdminMessageService,
+    private val adminMessageDao: AdminMessageDao,
+    private val appInfo: AppInfo,
+    private val refreshHelper: AutoRefreshHelper,
+) {
+    private val saveFetchResultMutex = Mutex()
+
+    private val cacheKey = "admin_messages"
+
+    suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
+        mutex = saveFetchResultMutex,
+        query = { adminMessageDao.loadAll() },
+        fetch = { adminMessageService.getAdminMessages() },
+        shouldFetch = {
+            refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
+        },
+        saveFetchResult = { oldItems, newItems ->
+            adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
+            refreshHelper.updateLastRefreshTimestamp(cacheKey)
+        },
+        showSavedOnLoading = false,
+        mapResult = { adminMessages ->
+            adminMessages.filter { adminMessage ->
+                val isCorrectRegister = adminMessage.targetRegisterHost?.let {
+                    student.scrapperBaseUrl.contains(it, true)
+                } ?: true
+                val isCorrectFlavor =
+                    adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
+                val isCorrectMaxVersion =
+                    adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
+                val isCorrectMinVersion =
+                    adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
+
+                isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
+            }.maxByOrNull { it.id }
+        }
+    )
+}
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt
index 71b7ea94..cbaa12bd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt
@@ -1,25 +1,27 @@
 package io.github.wulkanowy.data.repositories
 
-import android.content.res.AssetManager
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.Types
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.data.pojos.Contributor
 import io.github.wulkanowy.utils.DispatchersProvider
 import kotlinx.coroutines.withContext
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
 class AppCreatorRepository @Inject constructor(
-    private val assets: AssetManager,
-    private val dispatchers: DispatchersProvider
+    @ApplicationContext private val context: Context,
+    private val dispatchers: DispatchersProvider,
+    private val json: Json,
 ) {
 
+    @OptIn(ExperimentalSerializationApi::class)
     @Suppress("BlockingMethodInNonBlockingContext")
-    suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
-        val moshi = Moshi.Builder().build()
-        val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
-        val adapter = moshi.adapter<List<Contributor>>(type)
-        adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
+    suspend fun getAppCreators() = withContext(dispatchers.io) {
+        val inputStream = context.assets.open("contributors.json").buffered()
+        json.decodeFromStream<List<Contributor>>(inputStream)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
index d21ffb5f..ec919817 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt
@@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
 import io.github.wulkanowy.utils.networkBoundResource
 import io.github.wulkanowy.utils.sunday
 import io.github.wulkanowy.utils.uniqueSubtract
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.sync.Mutex
 import java.time.LocalDate
 import java.time.LocalDateTime
@@ -38,6 +39,7 @@ class AttendanceRepository @Inject constructor(
         start: LocalDate,
         end: LocalDate,
         forceRefresh: Boolean,
+        notify: Boolean = false,
     ) = networkBoundResource(
         mutex = saveFetchResultMutex,
         shouldFetch = {
@@ -56,13 +58,28 @@ class AttendanceRepository @Inject constructor(
         },
         saveFetchResult = { old, new ->
             attendanceDb.deleteAll(old uniqueSubtract new)
-            attendanceDb.insertAll(new uniqueSubtract old)
+            val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
+                newAttendance.apply { if (notify) isNotified = false }
+            }
+            attendanceDb.insertAll(attendanceToAdd)
 
             refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
         },
         filterResult = { it.filter { item -> item.date in start..end } }
     )
 
+    fun getAttendanceFromDatabase(
+        semester: Semester,
+        start: LocalDate,
+        end: LocalDate
+    ): Flow<List<Attendance>> {
+        return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end)
+    }
+
+    suspend fun updateTimetable(timetable: List<Attendance>) {
+        return attendanceDb.updateAll(timetable)
+    }
+
     suspend fun excuseForAbsence(
         student: Student, semester: Semester,
         absenceList: List<Attendance>, reason: String? = null
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
index a04085fb..95a375a4 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt
@@ -61,8 +61,9 @@ class HomeworkRepository @Inject constructor(
             val homeWorkToSave = (new uniqueSubtract old).onEach {
                 if (notify) it.isNotified = false
             }
+            val filteredOld = old.filterNot { it.isAddedByUser }
 
-            homeworkDb.deleteAll(old uniqueSubtract new)
+            homeworkDb.deleteAll(filteredOld uniqueSubtract new)
             homeworkDb.insertAll(homeWorkToSave)
 
             refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
@@ -79,4 +80,8 @@ class HomeworkRepository @Inject constructor(
         homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
 
     suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
+
+    suspend fun saveHomework(homework: Homework) = homeworkDb.insertAll(listOf(homework))
+
+    suspend fun deleteHomework(homework: Homework) = homeworkDb.deleteAll(listOf(homework))
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt
index 6d509b02..1a8cd6ea 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt
@@ -15,24 +15,23 @@ class LoggerRepository @Inject constructor(
 
     suspend fun getLastLogLines() = getLastModified().readText().split("\n")
 
-    suspend fun getLogFiles() = withContext(dispatchers.backgroundThread) {
-        File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
-            it.name.endsWith(".log")
-        }!!
+    suspend fun getLogFiles() = withContext(dispatchers.io) {
+        File(context.filesDir.absolutePath).listFiles(File::isFile)
+            ?.filter { it.name.endsWith(".log") }!!
     }
 
-    private suspend fun getLastModified(): File {
-        return withContext(dispatchers.backgroundThread) {
-            var lastModifiedTime = Long.MIN_VALUE
-            var chosenFile: File? = null
-            File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file ->
+    private suspend fun getLastModified() = withContext(dispatchers.io) {
+        var lastModifiedTime = Long.MIN_VALUE
+        var chosenFile: File? = null
+
+        File(context.filesDir.absolutePath).listFiles(File::isFile)
+            ?.forEach { file ->
                 if (file.lastModified() > lastModifiedTime) {
                     lastModifiedTime = file.lastModified()
                     chosenFile = file
                 }
             }
-            if (chosenFile == null) throw FileNotFoundException("Log file not found")
-            chosenFile!!
-        }
+
+        chosenFile ?: throw FileNotFoundException("Log file not found")
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
index ee516495..224c69bd 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt
@@ -1,7 +1,6 @@
 package io.github.wulkanowy.data.repositories
 
 import android.content.Context
-import com.squareup.moshi.Moshi
 import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.Resource
@@ -18,7 +17,6 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
 import io.github.wulkanowy.data.mappers.mapFromEntities
 import io.github.wulkanowy.data.mappers.mapToEntities
 import io.github.wulkanowy.data.pojos.MessageDraft
-import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
 import io.github.wulkanowy.sdk.Sdk
 import io.github.wulkanowy.sdk.pojo.Folder
 import io.github.wulkanowy.sdk.pojo.SentMessage
@@ -29,6 +27,9 @@ import io.github.wulkanowy.utils.networkBoundResource
 import io.github.wulkanowy.utils.uniqueSubtract
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.sync.Mutex
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
 import timber.log.Timber
 import java.time.LocalDateTime.now
 import javax.inject.Inject
@@ -42,7 +43,7 @@ class MessageRepository @Inject constructor(
     @ApplicationContext private val context: Context,
     private val refreshHelper: AutoRefreshHelper,
     private val sharedPrefProvider: SharedPrefProvider,
-    private val moshi: Moshi,
+    private val json: Json,
 ) {
 
     private val saveFetchResultMutex = Mutex()
@@ -168,9 +169,9 @@ class MessageRepository @Inject constructor(
 
     var draftMessage: MessageDraft?
         get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
-            ?.let { MessageDraftJsonAdapter(moshi).fromJson(it) }
+            ?.let { json.decodeFromString(it) }
         set(value) = sharedPrefProvider.putString(
             context.getString(R.string.pref_key_message_send_draft),
-            value?.let { MessageDraftJsonAdapter(moshi).toJson(it) }
+            value?.let { json.encodeToString(it) }
         )
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
index a08045f8..696ffd63 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt
@@ -5,20 +5,23 @@ import android.content.SharedPreferences
 import androidx.core.content.edit
 import com.fredporciuncula.flow.preferences.FlowSharedPreferences
 import com.fredporciuncula.flow.preferences.Preference
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.adapter
 import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.sdk.toLocalDate
 import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
 import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
 import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
 import io.github.wulkanowy.utils.toLocalDateTime
 import io.github.wulkanowy.utils.toTimestamp
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import java.lang.ClassCastException
+import java.lang.IllegalStateException
 import java.time.LocalDate
 import java.time.LocalDateTime
 import javax.inject.Inject
@@ -27,16 +30,12 @@ import javax.inject.Singleton
 @OptIn(ExperimentalCoroutinesApi::class)
 @Singleton
 class PreferencesRepository @Inject constructor(
+    @ApplicationContext val context: Context,
     private val sharedPref: SharedPreferences,
     private val flowSharedPref: FlowSharedPreferences,
-    @ApplicationContext val context: Context,
-    moshi: Moshi
+    private val json: Json,
 ) {
 
-    @OptIn(ExperimentalStdlibApi::class)
-    private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
-        moshi.adapter()
-
     val startMenuIndex: Int
         get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
 
@@ -60,8 +59,13 @@ class PreferencesRepository @Inject constructor(
             R.bool.pref_default_grade_average_force_calc
         )
 
-    val isGradeExpandable: Boolean
-        get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
+    val gradeExpandMode: GradeExpandMode
+        get() = GradeExpandMode.getByValue(
+            getString(
+                R.string.pref_key_expand_grade_mode,
+                R.string.pref_default_expand_grade_mode
+            )
+        )
 
     val showAllSubjectsOnStatisticsList: Boolean
         get() = getBoolean(
@@ -197,14 +201,14 @@ class PreferencesRepository @Inject constructor(
 
     var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
         get() {
-            val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
+            val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
 
-            return dashboardItemsPositionAdapter.fromJson(json)
+            return json.decodeFromString(value)
         }
         set(value) = sharedPref.edit {
             putString(
                 PREF_KEY_DASHBOARD_ITEMS_POSITION,
-                dashboardItemsPositionAdapter.toJson(value)
+                json.encodeToString(value)
             )
         }
 
@@ -213,6 +217,7 @@ class PreferencesRepository @Inject constructor(
             .map { set ->
                 set.map { DashboardItem.Tile.valueOf(it) }
                     .plus(DashboardItem.Tile.ACCOUNT)
+                    .plus(DashboardItem.Tile.ADMIN_MESSAGE)
                     .toSet()
             }
 
@@ -220,6 +225,7 @@ class PreferencesRepository @Inject constructor(
         get() = selectedDashboardTilesPreference.get()
             .map { DashboardItem.Tile.valueOf(it) }
             .plus(DashboardItem.Tile.ACCOUNT)
+            .plus(DashboardItem.Tile.ADMIN_MESSAGE)
             .toSet()
         set(value) {
             val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
@@ -267,6 +273,9 @@ class PreferencesRepository @Inject constructor(
     private fun getBoolean(id: String, default: Int) =
         sharedPref.getBoolean(id, context.resources.getBoolean(default))
 
+    private fun getBoolean(id: Int, default: Boolean) =
+        sharedPref.getBoolean(context.getString(id), default)
+
     private companion object {
 
         private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
index 4336877a..cc954558 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt
@@ -26,7 +26,7 @@ class SemesterRepository @Inject constructor(
         student: Student,
         forceRefresh: Boolean = false,
         refreshOnNoCurrent: Boolean = false
-    ) = withContext(dispatchers.backgroundThread) {
+    ) = withContext(dispatchers.io) {
         val semesters = semesterDb.loadAll(student.studentId, student.classId)
 
         if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
@@ -64,7 +64,7 @@ class SemesterRepository @Inject constructor(
     }
 
     suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
-        withContext(dispatchers.backgroundThread) {
+        withContext(dispatchers.io) {
             getSemesters(student, forceRefresh).getCurrentOrLast()
         }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
index 2ac892d0..9e4a1aab 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt
@@ -66,7 +66,7 @@ class StudentRepository @Inject constructor(
             .map {
                 it.apply {
                     if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
-                        student.password = withContext(dispatchers.backgroundThread) {
+                        student.password = withContext(dispatchers.io) {
                             decrypt(student.password)
                         }
                     }
@@ -77,7 +77,7 @@ class StudentRepository @Inject constructor(
         val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
 
         if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
-            student.password = withContext(dispatchers.backgroundThread) {
+            student.password = withContext(dispatchers.io) {
                 decrypt(student.password)
             }
         }
@@ -88,7 +88,7 @@ class StudentRepository @Inject constructor(
         val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
 
         if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
-            student.password = withContext(dispatchers.backgroundThread) {
+            student.password = withContext(dispatchers.io) {
                 decrypt(student.password)
             }
         }
@@ -101,7 +101,7 @@ class StudentRepository @Inject constructor(
             .map {
                 it.apply {
                     if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
-                        password = withContext(dispatchers.backgroundThread) {
+                        password = withContext(dispatchers.io) {
                             encrypt(password, context)
                         }
                     }
diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
index 769fa0f0..8be62112 100644
--- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
+++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt
@@ -47,6 +47,7 @@ class TimetableRepository @Inject constructor(
         end: LocalDate,
         forceRefresh: Boolean,
         refreshAdditional: Boolean = false,
+        notify: Boolean = false
     ) = networkBoundResource(
         mutex = saveFetchResultMutex,
         shouldFetch = { (timetable, additional, headers) ->
@@ -67,7 +68,7 @@ class TimetableRepository @Inject constructor(
             timetableFull.mapToEntities(semester)
         },
         saveFetchResult = { timetableOld, timetableNew ->
-            refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
+            refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify)
             refreshAdditional(timetableOld.additional, timetableNew.additional)
             refreshDayHeaders(timetableOld.headers, timetableNew.headers)
 
@@ -117,13 +118,28 @@ class TimetableRepository @Inject constructor(
         }
     }
 
+    fun getTimetableFromDatabase(
+        semester: Semester,
+        from: LocalDate,
+        end: LocalDate
+    ): Flow<List<Timetable>> {
+        return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
+    }
+
+    suspend fun updateTimetable(timetable: List<Timetable>) {
+        return timetableDb.updateAll(timetable)
+    }
+
     private suspend fun refreshTimetable(
         student: Student,
         lessonsOld: List<Timetable>,
         lessonsNew: List<Timetable>,
+        notify: Boolean
     ) {
         val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
-        val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld
+        val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
+            new.apply { if (notify) isNotified = false }
+        }
 
         timetableDb.deleteAll(lessonsToRemove)
         timetableDb.insertAll(lessonsToAdd)
diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt
index cdf0c26a..1729f100 100644
--- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt
@@ -15,6 +15,7 @@ import dagger.multibindings.IntoSet
 import io.github.wulkanowy.services.sync.channels.Channel
 import io.github.wulkanowy.services.sync.channels.DebugChannel
 import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
+import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
 import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
 import io.github.wulkanowy.services.sync.channels.NewExamChannel
 import io.github.wulkanowy.services.sync.channels.NewGradesChannel
@@ -23,6 +24,7 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
 import io.github.wulkanowy.services.sync.channels.NewNotesChannel
 import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
 import io.github.wulkanowy.services.sync.channels.PushChannel
+import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
 import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
 import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
 import io.github.wulkanowy.services.sync.works.AttendanceWork
@@ -167,4 +169,12 @@ abstract class ServicesModule {
     @Binds
     @IntoSet
     abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
+
+    @Binds
+    @IntoSet
+    abstract fun provideChangeTimetableChannel(channel: TimetableChangeChannel): Channel
+
+    @Binds
+    @IntoSet
+    abstract fun provideNewAttendanceChannel(channel: NewAttendanceChannel): Channel
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt
index 5e4bad8c..b388d2ac 100644
--- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt
@@ -1,7 +1,6 @@
 package io.github.wulkanowy.services.alarm
 
 import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.content.Context
 import android.content.Intent
 import android.os.Build
@@ -15,8 +14,9 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
 import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.services.HiltBroadcastReceiver
 import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
-import io.github.wulkanowy.ui.modules.main.MainActivity
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.PendingIntentCompat
 import io.github.wulkanowy.utils.flowWithResource
 import io.github.wulkanowy.utils.getCompatColor
 import io.github.wulkanowy.utils.toLocalDateTime
@@ -41,7 +41,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
         const val NOTIFICATION_TYPE_UPCOMING = 2
         const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
 
-        const val NOTIFICATION_ID = "id"
+        const val NOTIFICATION_ID = 2137
 
         const val STUDENT_NAME = "student_name"
         const val STUDENT_ID = "student_id"
@@ -71,11 +71,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
 
     private fun prepareNotification(context: Context, intent: Intent) {
         val type = intent.getIntExtra(LESSON_TYPE, 0)
-        val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
         val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
 
         if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
-            return NotificationManagerCompat.from(context).cancel(notificationId)
+            return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
         }
 
         val studentId = intent.getIntExtra(STUDENT_ID, 0)
@@ -92,7 +91,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
 
         Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
 
-        showNotification(context, notificationId, isPersistent, studentName,
+        showNotification(
+            context, isPersistent, studentName,
             if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
             context.getString(
                 if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
@@ -109,7 +109,6 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
 
     private fun showNotification(
         context: Context,
-        notificationId: Int,
         isPersistent: Boolean,
         studentName: String?,
         countDown: Long,
@@ -118,12 +117,13 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
         next: String?
     ) {
         NotificationManagerCompat.from(context)
-            .notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
+            .notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID)
                 .setContentTitle(title)
                 .setContentText(next)
                 .setAutoCancel(false)
                 .setWhen(countDown)
                 .setOngoing(isPersistent)
+                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                 .apply {
                     if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
                 }
@@ -137,9 +137,9 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
                 .setContentIntent(
                     PendingIntent.getActivity(
                         context,
-                        MainView.Section.TIMETABLE.id,
-                        MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
-                        FLAG_UPDATE_CURRENT
+                        NOTIFICATION_ID,
+                        SplashActivity.getStartIntent(context, Destination.Timetable()),
+                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
                     )
                 )
                 .build()
diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
index a42a0ab4..6e39b910 100644
--- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt
@@ -3,7 +3,6 @@ package io.github.wulkanowy.services.alarm
 import android.app.AlarmManager
 import android.app.AlarmManager.RTC_WAKEUP
 import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.content.Context
 import android.content.Intent
 import androidx.core.app.AlarmManagerCompat
@@ -25,8 +24,8 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
 import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
 import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
 import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
-import io.github.wulkanowy.ui.modules.main.MainView
 import io.github.wulkanowy.utils.DispatchersProvider
+import io.github.wulkanowy.utils.PendingIntentCompat
 import io.github.wulkanowy.utils.nickOrName
 import io.github.wulkanowy.utils.toTimestamp
 import kotlinx.coroutines.withContext
@@ -54,7 +53,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
 
     suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
         val studentId = student.studentId
-        withContext(dispatchersProvider.backgroundThread) {
+        withContext(dispatchersProvider.io) {
             lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
                 val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
                 cancelScheduledTo(
@@ -73,13 +72,19 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
 
     private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
         if (now() in range) cancelNotification()
+
         alarmManager.cancel(
-            PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
+            PendingIntent.getBroadcast(
+                context,
+                requestCode,
+                Intent(),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+            )
         )
     }
 
     fun cancelNotification() =
-        NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
+        NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
 
     suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
         if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
@@ -91,7 +96,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
             return
         }
 
-        withContext(dispatchersProvider.backgroundThread) {
+        withContext(dispatchersProvider.io) {
             lessons.groupBy { it.date }
                 .map { it.value.sortedBy { lesson -> lesson.start } }
                 .map { it.filter { lesson -> lesson.isStudentPlan } }
@@ -156,9 +161,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
             AlarmManagerCompat.setExactAndAllowWhileIdle(
                 alarmManager, RTC_WAKEUP, time.toTimestamp(),
                 PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
-                    it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
                     it.putExtra(LESSON_TYPE, notificationType)
-                }, FLAG_UPDATE_CURRENT)
+                }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
             )
             Timber.d(
                 "TimetableNotification scheduled: type: $notificationType, subject: ${
diff --git a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt
new file mode 100644
index 00000000..4ad9ac12
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt
@@ -0,0 +1,90 @@
+package io.github.wulkanowy.services.shortcuts
+
+import android.content.Context
+import android.content.Intent
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.R
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
+
+    private val destinations = mapOf(
+        "grade" to Destination.Grade,
+        "attendance" to Destination.Attendance,
+        "exam" to Destination.Exam,
+        "timetable" to Destination.Timetable()
+    )
+
+    init {
+        initializeShortcuts()
+    }
+
+    fun getDestination(intent: Intent) =
+        destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)]
+
+    private fun initializeShortcuts() {
+        val shortcutsInfo = listOf(
+            ShortcutInfoCompat.Builder(context, "grade_shortcut")
+                .setShortLabel(context.getString(R.string.grade_title))
+                .setLongLabel(context.getString(R.string.grade_title))
+                .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
+                .setIntent(SplashActivity.getStartIntent(context)
+                    .apply {
+                        action = Intent.ACTION_VIEW
+                        putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade")
+                    }
+                )
+                .build(),
+
+            ShortcutInfoCompat.Builder(context, "attendance_shortcut")
+                .setShortLabel(context.getString(R.string.attendance_title))
+                .setLongLabel(context.getString(R.string.attendance_title))
+                .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
+                .setIntent(SplashActivity.getStartIntent(context)
+                    .apply {
+                        action = Intent.ACTION_VIEW
+                        putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance")
+                    }
+                )
+                .build(),
+
+            ShortcutInfoCompat.Builder(context, "exam_shortcut")
+                .setShortLabel(context.getString(R.string.exam_title))
+                .setLongLabel(context.getString(R.string.exam_title))
+                .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
+                .setIntent(SplashActivity.getStartIntent(context)
+                    .apply {
+                        action = Intent.ACTION_VIEW
+                        putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam")
+                    }
+                )
+                .build(),
+
+            ShortcutInfoCompat.Builder(context, "timetable_shortcut")
+                .setShortLabel(context.getString(R.string.timetable_title))
+                .setLongLabel(context.getString(R.string.timetable_title))
+                .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
+                .setIntent(SplashActivity.getStartIntent(context)
+                    .apply {
+                        action = Intent.ACTION_VIEW
+                        putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable")
+                    }
+                )
+                .build()
+        )
+
+        shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
+    }
+
+    private companion object {
+
+        private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id"
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
index 02d8b964..32ca20af 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt
@@ -74,7 +74,7 @@ class SyncManager @Inject constructor(
         }
     }
 
-    fun startOneTimeSyncWorker(): Flow<WorkInfo> {
+    fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
         val work = OneTimeWorkRequestBuilder<SyncWorker>()
             .setInputData(
                 Data.Builder()
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
index ea1f79cb..52979e63 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt
@@ -19,11 +19,11 @@ import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
 import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
 import io.github.wulkanowy.services.sync.channels.DebugChannel
 import io.github.wulkanowy.services.sync.works.Work
+import io.github.wulkanowy.utils.DispatchersProvider
 import io.github.wulkanowy.utils.getCompatColor
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.withContext
 import timber.log.Timber
 import java.time.LocalDateTime
-import java.time.ZoneId
 import kotlin.random.Random
 
 @HiltWorker
@@ -34,13 +34,14 @@ class SyncWorker @AssistedInject constructor(
     private val semesterRepository: SemesterRepository,
     private val works: Set<@JvmSuppressWildcards Work>,
     private val preferencesRepository: PreferencesRepository,
-    private val notificationManager: NotificationManagerCompat
+    private val notificationManager: NotificationManagerCompat,
+    private val dispatchersProvider: DispatchersProvider
 ) : CoroutineWorker(appContext, workerParameters) {
 
-    override suspend fun doWork() = coroutineScope {
+    override suspend fun doWork() = withContext(dispatchersProvider.io) {
         Timber.i("SyncWorker is starting")
 
-        if (!studentRepository.isCurrentStudentSet()) return@coroutineScope Result.failure()
+        if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
 
         val student = studentRepository.getCurrentStudent()
         val semester = semesterRepository.getCurrentSemester(student, true)
@@ -50,12 +51,12 @@ class SyncWorker @AssistedInject constructor(
                 Timber.i("${work::class.java.simpleName} is starting")
                 work.doWork(student, semester)
                 Timber.i("${work::class.java.simpleName} result: Success")
-                preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
                 null
             } catch (e: Throwable) {
                 Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
-                if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
-                else {
+                if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
+                    null
+                } else {
                     Timber.e(e)
                     e
                 }
@@ -70,13 +71,16 @@ class SyncWorker @AssistedInject constructor(
                 )
             }
             exceptions.isNotEmpty() -> Result.retry()
-            else -> Result.success()
+            else -> {
+                preferencesRepository.lasSyncDate = LocalDateTime.now()
+                Result.success()
+            }
         }
 
         if (preferencesRepository.isDebugNotificationEnable) notify(result)
         Timber.i("SyncWorker result: $result")
 
-        result
+        return@withContext result
     }
 
     private fun notify(result: Result) {
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt
new file mode 100644
index 00000000..3110099e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt
@@ -0,0 +1,36 @@
+package io.github.wulkanowy.services.sync.channels
+
+import android.annotation.TargetApi
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import androidx.core.app.NotificationManagerCompat
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.R
+import javax.inject.Inject
+
+@TargetApi(26)
+class NewAttendanceChannel @Inject constructor(
+    private val notificationManager: NotificationManagerCompat,
+    @ApplicationContext private val context: Context
+) : Channel {
+
+    companion object {
+        const val CHANNEL_ID = "new_attendance_channel"
+    }
+
+    override fun create() {
+        notificationManager.createNotificationChannel(
+            NotificationChannel(
+                CHANNEL_ID,
+                context.getString(R.string.channel_new_attendance),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+                .apply {
+                    enableLights(true)
+                    enableVibration(true)
+                    lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+                })
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt
new file mode 100644
index 00000000..10dd3e00
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt
@@ -0,0 +1,36 @@
+package io.github.wulkanowy.services.sync.channels
+
+import android.annotation.TargetApi
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import androidx.core.app.NotificationManagerCompat
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.R
+import javax.inject.Inject
+
+@TargetApi(26)
+class TimetableChangeChannel @Inject constructor(
+    private val notificationManager: NotificationManagerCompat,
+    @ApplicationContext private val context: Context
+) : Channel {
+
+    companion object {
+        const val CHANNEL_ID = "change_timetable_channel"
+    }
+
+    override fun create() {
+        notificationManager.createNotificationChannel(
+            NotificationChannel(
+                CHANNEL_ID,
+                context.getString(R.string.channel_change_timetable),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+                .apply {
+                    enableLights(true)
+                    enableVibration(true)
+                    lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+                })
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt
index 69d0092c..e276df0c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt
@@ -10,12 +10,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Notification
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
+import io.github.wulkanowy.data.pojos.GroupNotificationData
 import io.github.wulkanowy.data.pojos.NotificationData
-import io.github.wulkanowy.data.pojos.OneNotificationData
 import io.github.wulkanowy.data.repositories.NotificationRepository
-import io.github.wulkanowy.ui.modules.main.MainActivity
-import io.github.wulkanowy.utils.AppInfo
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.utils.PendingIntentCompat
 import io.github.wulkanowy.utils.getCompatBitmap
 import io.github.wulkanowy.utils.getCompatColor
 import io.github.wulkanowy.utils.nickOrName
@@ -26,120 +25,156 @@ import kotlin.random.Random
 class AppNotificationManager @Inject constructor(
     private val notificationManager: NotificationManagerCompat,
     @ApplicationContext private val context: Context,
-    private val appInfo: AppInfo,
+    private val studentRepository: StudentRepository,
     private val notificationRepository: NotificationRepository
 ) {
 
-    suspend fun sendNotification(notificationData: NotificationData, student: Student) =
-        when (notificationData) {
-            is OneNotificationData -> sendOneNotification(notificationData, student)
-            is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student)
-        }
-
-    private suspend fun sendOneNotification(
-        notificationData: OneNotificationData,
-        student: Student
-    ) {
-        val content = context.getString(
-            notificationData.contentStringRes,
-            *notificationData.contentValues.toTypedArray()
-        )
-
-        val title = context.getString(notificationData.titleStringRes)
-
-        val notification = getDefaultNotificationBuilder(notificationData)
-            .setContentTitle(title)
-            .setContentText(content)
-            .setStyle(
-                NotificationCompat.BigTextStyle()
-                    .setSummaryText(student.nickOrName)
-                    .bigText(content)
-            )
-            .build()
-
-        notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
-
-        saveNotification(title, content, notificationData, student)
-    }
-
-    private suspend fun sendMultipleNotifications(
-        notificationData: MultipleNotificationsData,
-        student: Student
-    ) {
-        val groupType = notificationData.type.group ?: return
-        val group = "${groupType}_${student.id}"
-        val groupId = student.id * 100 + notificationData.type.ordinal
-
-        notificationData.lines.forEach { item ->
-            val title = context.resources.getQuantityString(notificationData.titleStringRes, 1)
-
-            val notification = getDefaultNotificationBuilder(notificationData)
-                .setContentTitle(title)
-                .setContentText(item)
-                .setStyle(
-                    NotificationCompat.BigTextStyle()
-                        .setSummaryText(student.nickOrName)
-                        .bigText(item)
-                )
-                .setGroup(group)
-                .build()
-
-            notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
-
-            saveNotification(title, item, notificationData, student)
-        }
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
-
-        val summaryNotification = getDefaultNotificationBuilder(notificationData)
-            .setSmallIcon(notificationData.icon)
-            .setGroup(group)
-            .setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
-            .setGroupSummary(true)
-            .build()
-
-        notificationManager.notify(groupId.toInt(), summaryNotification)
-    }
-
     @SuppressLint("InlinedApi")
-    private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder {
-        val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) {
-            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
-        } else {
-            PendingIntent.FLAG_UPDATE_CURRENT
-        }
-
-        return NotificationCompat.Builder(context, notificationData.type.channel)
-            .setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary))
+    suspend fun sendSingleNotification(
+        notificationData: NotificationData,
+        notificationType: NotificationType,
+        student: Student
+    ) {
+        val notification = NotificationCompat.Builder(context, notificationType.channel)
+            .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
             .setSmallIcon(R.drawable.ic_stat_all)
             .setAutoCancel(true)
             .setDefaults(NotificationCompat.DEFAULT_ALL)
             .setPriority(NotificationCompat.PRIORITY_HIGH)
             .setColor(context.getCompatColor(R.color.colorPrimary))
+            .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
             .setContentIntent(
                 PendingIntent.getActivity(
                     context,
-                    notificationData.startMenu.id,
-                    MainActivity.getStartIntent(context, notificationData.startMenu, true),
-                    pendingIntentsFlags
+                    Random.nextInt(),
+                    notificationData.intentToStart,
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
                 )
             )
+            .setContentTitle(notificationData.title)
+            .setContentText(notificationData.content)
+            .setStyle(
+                NotificationCompat.BigTextStyle()
+                    .bigText(notificationData.content)
+                    .also { builder ->
+                        if (shouldShowStudentName()) {
+                            builder.setSummaryText(student.nickOrName)
+                        }
+                    }
+            )
+            .build()
+
+        notificationManager.notify(Random.nextInt(), notification)
+        saveNotification(notificationData, notificationType, student)
+    }
+
+    @SuppressLint("InlinedApi")
+    suspend fun sendMultipleNotifications(
+        groupNotificationData: GroupNotificationData,
+        student: Student
+    ) {
+        val notificationType = groupNotificationData.type
+        val groupType = notificationType.group ?: return
+        val group = "${groupType}_${student.id}"
+
+        sendSummaryNotification(groupNotificationData, group, student)
+
+        groupNotificationData.notificationDataList.forEach { notificationData ->
+            val notification = NotificationCompat.Builder(context, notificationType.channel)
+                .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
+                .setSmallIcon(R.drawable.ic_stat_all)
+                .setAutoCancel(true)
+                .setDefaults(NotificationCompat.DEFAULT_ALL)
+                .setPriority(NotificationCompat.PRIORITY_HIGH)
+                .setColor(context.getCompatColor(R.color.colorPrimary))
+                .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
+                .setContentIntent(
+                    PendingIntent.getActivity(
+                        context,
+                        Random.nextInt(),
+                        notificationData.intentToStart,
+                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+                    )
+                )
+                .setContentTitle(notificationData.title)
+                .setContentText(notificationData.content)
+                .setStyle(
+                    NotificationCompat.BigTextStyle()
+                        .bigText(notificationData.content)
+                        .also { builder ->
+                            if (shouldShowStudentName()) {
+                                builder.setSummaryText(student.nickOrName)
+                            }
+                        }
+                )
+                .setGroup(group)
+                .build()
+
+            notificationManager.notify(Random.nextInt(), notification)
+            saveNotification(notificationData, groupNotificationData.type, student)
+        }
+    }
+
+    private suspend fun sendSummaryNotification(
+        groupNotificationData: GroupNotificationData,
+        group: String,
+        student: Student
+    ) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
+
+        val summaryNotification =
+            NotificationCompat.Builder(context, groupNotificationData.type.channel)
+                .setContentTitle(groupNotificationData.title)
+                .setContentText(groupNotificationData.content)
+                .setSmallIcon(groupNotificationData.type.icon)
+                .setAutoCancel(true)
+                .setDefaults(NotificationCompat.DEFAULT_ALL)
+                .setPriority(NotificationCompat.PRIORITY_HIGH)
+                .setColor(context.getCompatColor(R.color.colorPrimary))
+                .setStyle(
+                    NotificationCompat.InboxStyle()
+                        .also { builder ->
+                            if (shouldShowStudentName()) {
+                                builder.setSummaryText(student.nickOrName)
+                            }
+                            groupNotificationData.notificationDataList.forEach {
+                                builder.addLine(it.content)
+                            }
+                        }
+                )
+                .setContentIntent(
+                    PendingIntent.getActivity(
+                        context,
+                        Random.nextInt(),
+                        groupNotificationData.intentToStart,
+                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+                    )
+                )
+                .setLocalOnly(true)
+                .setGroup(group)
+                .setGroupSummary(true)
+                .build()
+
+        val groupId = student.id * 100 + groupNotificationData.type.ordinal
+        notificationManager.notify(groupId.toInt(), summaryNotification)
     }
 
     private suspend fun saveNotification(
-        title: String,
-        content: String,
         notificationData: NotificationData,
+        notificationType: NotificationType,
         student: Student
     ) {
         val notificationEntity = Notification(
             studentId = student.id,
-            title = title,
-            content = content,
-            type = notificationData.type,
+            title = notificationData.title,
+            content = notificationData.content,
+            type = notificationType,
             date = LocalDateTime.now()
         )
 
         notificationRepository.saveNotification(notificationEntity)
     }
-}
\ No newline at end of file
+
+    private suspend fun shouldShowStudentName(): Boolean =
+        studentRepository.getSavedStudents(decryptPass = false).size > 1
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt
new file mode 100644
index 00000000..7bfef96a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt
@@ -0,0 +1,124 @@
+package io.github.wulkanowy.services.sync.notifications
+
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.db.entities.Timetable
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
+import io.github.wulkanowy.utils.toFormattedString
+import java.time.LocalDate
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class ChangeTimetableNotification @Inject constructor(
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context,
+) {
+
+    suspend fun notify(items: List<Timetable>, student: Student) {
+        val currentTime = LocalDateTime.now()
+        val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
+        val notificationDataList = changedLessons.groupBy { it.date }
+            .map { (date, lessons) ->
+                getNotificationContents(date, lessons).map {
+                    NotificationData(
+                        title = context.getPlural(
+                            R.plurals.timetable_notify_new_items_title,
+                            1
+                        ),
+                        content = it,
+                        intentToStart = SplashActivity.getStartIntent(
+                            context = context,
+                            destination = Destination.Timetable(date)
+                        )
+                    )
+                }
+            }
+            .flatten()
+            .ifEmpty { return }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(
+                R.plurals.timetable_notify_new_items_title,
+                changedLessons.size
+            ),
+            content = context.getPlural(
+                R.plurals.timetable_notify_new_items_group,
+                changedLessons.size,
+                changedLessons.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()),
+            type = NotificationType.CHANGE_TIMETABLE
+        )
+
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
+    }
+
+    private fun getNotificationContents(date: LocalDate, lessons: List<Timetable>): List<String> {
+        val formattedDate = date.toFormattedString("EEE dd.MM")
+
+        return if (lessons.size > 2) {
+            listOf(
+                context.getPlural(
+                    R.plurals.timetable_notify_new_items,
+                    lessons.size,
+                    formattedDate,
+                    lessons.size,
+                )
+            )
+        } else {
+            lessons.map {
+                buildString {
+                    append(
+                        context.getString(
+                            R.string.timetable_notify_lesson,
+                            formattedDate,
+                            it.number,
+                            it.subject
+                        )
+                    )
+                    if (it.roomOld.isNotBlank()) {
+                        appendLine()
+                        append(
+                            context.getString(
+                                R.string.timetable_notify_change_room,
+                                it.roomOld,
+                                it.room
+                            )
+                        )
+                    }
+                    if (it.teacherOld.isNotBlank() && it.teacher != it.teacherOld) {
+                        appendLine()
+                        append(
+                            context.getString(
+                                R.string.timetable_notify_change_teacher,
+                                it.teacherOld,
+                                it.teacher
+                            )
+                        )
+                    }
+                    if (it.subjectOld.isNotBlank()) {
+                        appendLine()
+                        append(
+                            context.getString(
+                                R.string.timetable_notify_change_subject,
+                                it.subjectOld,
+                                it.subject
+                            )
+                        )
+                    }
+                    if (it.info.isNotBlank()) {
+                        appendLine()
+                        append(it.info)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt
new file mode 100644
index 00000000..c78dcd05
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt
@@ -0,0 +1,55 @@
+package io.github.wulkanowy.services.sync.notifications
+
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.R
+import io.github.wulkanowy.data.db.entities.Attendance
+import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.descriptionRes
+import io.github.wulkanowy.utils.getPlural
+import io.github.wulkanowy.utils.toFormattedString
+import javax.inject.Inject
+
+class NewAttendanceNotification @Inject constructor(
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
+) {
+
+    suspend fun notify(items: List<Attendance>, student: Student) {
+        val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
+            .map {
+                val description = context.getString(it.descriptionRes)
+                "${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description"
+            }
+            .ifEmpty { return }
+
+        val notificationDataList = lines.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1),
+                content = it,
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance)
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(
+                R.plurals.attendance_notify_new_items_title,
+                notificationDataList.size
+            ),
+            content = context.getPlural(
+                R.plurals.attendance_notify_new_items,
+                notificationDataList.size,
+                notificationDataList.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance),
+            type = NotificationType.NEW_ATTENDANCE
+        )
+
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt
index 994cb8d4..8ef14788 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt
@@ -1,34 +1,52 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Conference
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import io.github.wulkanowy.utils.toFormattedString
 import java.time.LocalDateTime
 import javax.inject.Inject
 
 class NewConferenceNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notify(items: List<Conference>, student: Student) {
         val today = LocalDateTime.now()
-        val lines = items.filter { !it.date.isBefore(today) }.map {
-            "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
-        }.ifEmpty { return }
+        val lines = items.filter { !it.date.isBefore(today) }
+            .map {
+                "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
+            }
+            .ifEmpty { return }
 
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_CONFERENCE,
-            icon = R.drawable.ic_more_conferences,
-            titleStringRes = R.plurals.conference_notify_new_item_title,
-            contentStringRes = R.plurals.conference_notify_new_items,
-            summaryStringRes = R.plurals.conference_number_item,
-            startMenu = MainView.Section.CONFERENCE,
-            lines = lines
+        val notificationDataList = lines.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
+                content = it,
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.conference_notify_new_item_title, lines.size),
+            content = context.getPlural(
+                R.plurals.conference_notify_new_items,
+                lines.size,
+                lines.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Conference),
+            type = NotificationType.NEW_CONFERENCE
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt
index f148fa34..b3cf04c4 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt
@@ -1,34 +1,52 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Exam
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import io.github.wulkanowy.utils.toFormattedString
 import java.time.LocalDate
 import javax.inject.Inject
 
 class NewExamNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notify(items: List<Exam>, student: Student) {
         val today = LocalDate.now()
-        val lines = items.filter { !it.date.isBefore(today) }.map {
-            "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
-        }.ifEmpty { return }
+        val lines = items.filter { !it.date.isBefore(today) }
+            .map {
+                "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
+            }
+            .ifEmpty { return }
 
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_EXAM,
-            icon = R.drawable.ic_main_exam,
-            titleStringRes = R.plurals.exam_notify_new_item_title,
-            contentStringRes = R.plurals.exam_notify_new_item_content,
-            summaryStringRes = R.plurals.exam_number_item,
-            startMenu = MainView.Section.EXAM,
-            lines = lines
+        val notificationDataList = lines.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
+                content = it,
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.exam_notify_new_item_title, lines.size),
+            content = context.getPlural(
+                R.plurals.exam_notify_new_item_content,
+                lines.size,
+                lines.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
+            type = NotificationType.NEW_EXAM
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
index 52bdff58..39ecbe33 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt
@@ -1,62 +1,88 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Grade
 import io.github.wulkanowy.data.db.entities.GradeSummary
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import javax.inject.Inject
 
 class NewGradeNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notifyDetails(items: List<Grade>, student: Student) {
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_GRADE_DETAILS,
-            icon = R.drawable.ic_stat_grade,
-            titleStringRes = R.plurals.grade_new_items,
-            contentStringRes = R.plurals.grade_notify_new_items,
-            summaryStringRes = R.plurals.grade_number_item,
-            startMenu = MainView.Section.GRADE,
-            lines = items.map {
-                "${it.subject}: ${it.entry}"
-            }
+        val notificationDataList = items.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.grade_new_items, 1),
+                content = "${it.subject}: ${it.entry}",
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.grade_new_items, items.size),
+            content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            type = NotificationType.NEW_GRADE_DETAILS
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 
     suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_GRADE_PREDICTED,
-            icon = R.drawable.ic_stat_grade,
-            titleStringRes = R.plurals.grade_new_items_predicted,
-            contentStringRes = R.plurals.grade_notify_new_items_predicted,
-            summaryStringRes = R.plurals.grade_number_item,
-            startMenu = MainView.Section.GRADE,
-            lines = items.map {
-                "${it.subject}: ${it.predictedGrade}"
-            }
+        val notificationDataList = items.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
+                content = "${it.subject}: ${it.predictedGrade}",
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.grade_new_items_predicted, items.size),
+            content = context.getPlural(
+                R.plurals.grade_notify_new_items_predicted,
+                items.size,
+                items.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            type = NotificationType.NEW_GRADE_PREDICTED
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 
     suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_GRADE_FINAL,
-            icon = R.drawable.ic_stat_grade,
-            titleStringRes = R.plurals.grade_new_items_final,
-            contentStringRes = R.plurals.grade_notify_new_items_final,
-            summaryStringRes = R.plurals.grade_number_item,
-            startMenu = MainView.Section.GRADE,
-            lines = items.map {
-                "${it.subject}: ${it.finalGrade}"
-            }
+        val notificationDataList = items.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.grade_new_items_final, 1),
+                content = "${it.subject}: ${it.finalGrade}",
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.grade_new_items_final, items.size),
+            content = context.getPlural(
+                R.plurals.grade_notify_new_items_final,
+                items.size,
+                items.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
+            type = NotificationType.NEW_GRADE_FINAL
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt
index 4c34cb8f..ff32aa66 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt
@@ -1,34 +1,52 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Homework
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import io.github.wulkanowy.utils.toFormattedString
 import java.time.LocalDate
 import javax.inject.Inject
 
 class NewHomeworkNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notify(items: List<Homework>, student: Student) {
         val today = LocalDate.now()
-        val lines = items.filter { !it.date.isBefore(today) }.map {
-            "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
-        }.ifEmpty { return }
+        val lines = items.filter { !it.date.isBefore(today) }
+            .map {
+                "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
+            }
+            .ifEmpty { return }
 
-        val notification = MultipleNotificationsData(
+        val notificationDataList = lines.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.homework_notify_new_item_title, 1),
+                content = it,
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            title = context.getPlural(R.plurals.homework_notify_new_item_title, lines.size),
+            content = context.getPlural(
+                R.plurals.homework_notify_new_item_content,
+                lines.size,
+                lines.size
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
             type = NotificationType.NEW_HOMEWORK,
-            icon = R.drawable.ic_more_homework,
-            titleStringRes = R.plurals.homework_notify_new_item_title,
-            contentStringRes = R.plurals.homework_notify_new_item_content,
-            summaryStringRes = R.plurals.homework_number_item,
-            startMenu = MainView.Section.HOMEWORK,
-            lines = lines
+            notificationDataList = notificationDataList
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt
index 08c98510..5c36a06c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt
@@ -1,26 +1,34 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.LuckyNumber
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.OneNotificationData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
 import javax.inject.Inject
 
 class NewLuckyNumberNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
-   suspend fun notify(item: LuckyNumber, student: Student) {
-       val notification = OneNotificationData(
-           type = NotificationType.NEW_LUCKY_NUMBER,
-           icon = R.drawable.ic_stat_luckynumber,
-           titleStringRes = R.string.lucky_number_notify_new_item_title,
-           contentStringRes = R.string.lucky_number_notify_new_item,
-           startMenu = MainView.Section.LUCKY_NUMBER,
-           contentValues = listOf(item.luckyNumber.toString())
-       )
+    suspend fun notify(item: LuckyNumber, student: Student) {
+        val notificationData = NotificationData(
+            title = context.getString(R.string.lucky_number_notify_new_item_title),
+            content = context.getString(
+                R.string.lucky_number_notify_new_item,
+                item.luckyNumber.toString()
+            ),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber)
+        )
 
-       appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendSingleNotification(
+            notificationData = notificationData,
+            notificationType = NotificationType.NEW_LUCKY_NUMBER,
+            student = student
+        )
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt
index a6d503aa..b98d3466 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt
@@ -1,29 +1,39 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Message
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import javax.inject.Inject
 
 class NewMessageNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notify(items: List<Message>, student: Student) {
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_MESSAGE,
-            icon = R.drawable.ic_stat_message,
-            titleStringRes = R.plurals.message_new_items,
-            contentStringRes = R.plurals.message_notify_new_items,
-            summaryStringRes = R.plurals.message_number_item,
-            startMenu = MainView.Section.MESSAGE,
-            lines = items.map {
-                "${it.sender}: ${it.subject}"
-            }
+        val notificationDataList = items.map {
+            NotificationData(
+                title = context.getPlural(R.plurals.message_new_items, 1),
+                content = "${it.sender}: ${it.subject}",
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            title = context.getPlural(R.plurals.message_new_items, items.size),
+            content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size),
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
+            type = NotificationType.NEW_MESSAGE
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt
index ffa3cc9c..65520e01 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt
@@ -1,42 +1,46 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Note
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
 import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import javax.inject.Inject
 
 class NewNoteNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
     suspend fun notify(items: List<Note>, student: Student) {
-        val notification = MultipleNotificationsData(
-            type = NotificationType.NEW_NOTE,
-            icon = R.drawable.ic_stat_note,
-            titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
+        val notificationDataList = items.map {
+            val titleRes = when (NoteCategory.getByValue(it.categoryType)) {
                 NoteCategory.POSITIVE -> R.plurals.praise_new_items
                 NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
                 else -> R.plurals.note_new_items
-            },
-            contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
-                NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items
-                NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items
-                else -> R.plurals.note_notify_new_items
-            },
-            summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
-                NoteCategory.POSITIVE -> R.plurals.praise_number_item
-                NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item
-                else -> R.plurals.note_number_item
-            },
-            startMenu = MainView.Section.NOTE,
-            lines = items.map {
-                "${it.teacher}: ${it.category}"
             }
+
+            NotificationData(
+                title = context.getPlural(titleRes, 1),
+                content = "${it.teacher}: ${it.category}",
+                intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
+            )
+        }
+
+        val groupNotificationData = GroupNotificationData(
+            notificationDataList = notificationDataList,
+            intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
+            title = context.getPlural(R.plurals.note_new_items, items.size),
+            content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size),
+            type = NotificationType.NEW_NOTE
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt
index 990a950b..6b839d29 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt
@@ -1,29 +1,54 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
 import io.github.wulkanowy.data.db.entities.Student
-import io.github.wulkanowy.data.pojos.MultipleNotificationsData
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.data.pojos.GroupNotificationData
+import io.github.wulkanowy.data.pojos.NotificationData
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.getPlural
 import javax.inject.Inject
 
 class NewSchoolAnnouncementNotification @Inject constructor(
-    private val appNotificationManager: AppNotificationManager
+    private val appNotificationManager: AppNotificationManager,
+    @ApplicationContext private val context: Context
 ) {
 
-   suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
-       val notification = MultipleNotificationsData(
-           type = NotificationType.NEW_ANNOUNCEMENT,
-           icon = R.drawable.ic_all_about,
-           titleStringRes = R.plurals.school_announcement_notify_new_item_title,
-           contentStringRes = R.plurals.school_announcement_notify_new_items,
-           summaryStringRes = R.plurals.school_announcement_number_item,
-           startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
-           lines = items.map {
-               "${it.subject}: ${it.content}"
-           }
+    suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
+        val notificationDataList = items.map {
+            NotificationData(
+                intentToStart = SplashActivity.getStartIntent(
+                    context = context,
+                    destination = Destination.SchoolAnnouncement
+                ),
+                title = context.getPlural(
+                    R.plurals.school_announcement_notify_new_item_title,
+                    1
+                ),
+                content = "${it.subject}: ${it.content}"
+            )
+        }
+        val groupNotificationData = GroupNotificationData(
+            type = NotificationType.NEW_ANNOUNCEMENT,
+            intentToStart = SplashActivity.getStartIntent(
+                context = context,
+                destination = Destination.SchoolAnnouncement
+            ),
+            title = context.getPlural(
+                R.plurals.school_announcement_notify_new_item_title,
+                items.size
+            ),
+            content = context.getPlural(
+                R.plurals.school_announcement_notify_new_items,
+                items.size,
+                items.size
+            ),
+            notificationDataList = notificationDataList
         )
 
-        appNotificationManager.sendNotification(notification, student)
+        appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
index 49cbcfe9..af79fcd2 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt
@@ -1,6 +1,8 @@
 package io.github.wulkanowy.services.sync.notifications
 
+import io.github.wulkanowy.R
 import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
+import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
 import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
 import io.github.wulkanowy.services.sync.channels.NewExamChannel
 import io.github.wulkanowy.services.sync.channels.NewGradesChannel
@@ -9,17 +11,76 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
 import io.github.wulkanowy.services.sync.channels.NewNotesChannel
 import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
 import io.github.wulkanowy.services.sync.channels.PushChannel
+import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
 
-enum class NotificationType(val group: String?, val channel: String) {
-    NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
-    NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
-    NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
-    NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID),
-    NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID),
-    NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID),
-    NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID),
-    NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
-    NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
-    NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
-    PUSH(null, PushChannel.CHANNEL_ID)
+enum class NotificationType(
+    val group: String?,
+    val channel: String,
+    val icon: Int
+) {
+    NEW_CONFERENCE(
+        group = "new_conferences_group",
+        channel = NewConferencesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_more_conferences,
+    ),
+    NEW_EXAM(
+        group = "new_exam_group",
+        channel = NewExamChannel.CHANNEL_ID,
+        icon = R.drawable.ic_main_exam
+    ),
+    NEW_GRADE_DETAILS(
+        group = "new_grade_details_group",
+        channel = NewGradesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_grade,
+    ),
+    NEW_GRADE_PREDICTED(
+        group = "new_grade_predicted_group",
+        channel = NewGradesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_grade,
+    ),
+    NEW_GRADE_FINAL(
+        group = "new_grade_final_group",
+        channel = NewGradesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_grade,
+    ),
+    NEW_HOMEWORK(
+        group = "new_homework_group",
+        channel = NewHomeworkChannel.CHANNEL_ID,
+        icon = R.drawable.ic_more_homework,
+    ),
+    NEW_LUCKY_NUMBER(
+        group = null,
+        channel = LuckyNumberChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_luckynumber,
+    ),
+    NEW_MESSAGE(
+        group = "new_message_group",
+        channel = NewMessagesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_message,
+    ),
+    NEW_NOTE(
+        group = "new_notes_group",
+        channel = NewNotesChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_note
+    ),
+    NEW_ANNOUNCEMENT(
+        group = "new_school_announcements_group",
+        channel = NewSchoolAnnouncementsChannel.CHANNEL_ID,
+        icon = R.drawable.ic_all_about
+    ),
+    CHANGE_TIMETABLE(
+        group = "change_timetable_group",
+        channel = TimetableChangeChannel.CHANNEL_ID,
+        icon = R.drawable.ic_main_timetable
+    ),
+    NEW_ATTENDANCE(
+        group = "new_attendance_group",
+        channel = NewAttendanceChannel.CHANNEL_ID,
+        icon = R.drawable.ic_main_attendance
+    ),
+    PUSH(
+        group = null,
+        channel = PushChannel.CHANNEL_ID,
+        icon = R.drawable.ic_stat_all
+    )
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
index 4823b2b5..f7b680e3 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt
@@ -3,18 +3,40 @@ package io.github.wulkanowy.services.sync.works
 import io.github.wulkanowy.data.db.entities.Semester
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.repositories.AttendanceRepository
-import io.github.wulkanowy.utils.monday
-import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.data.repositories.PreferencesRepository
+import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
+import io.github.wulkanowy.utils.previousOrSameSchoolDay
 import io.github.wulkanowy.utils.waitForResult
+import kotlinx.coroutines.flow.first
 import java.time.LocalDate.now
 import javax.inject.Inject
 
 class AttendanceWork @Inject constructor(
-    private val attendanceRepository: AttendanceRepository
+    private val attendanceRepository: AttendanceRepository,
+    private val newAttendanceNotification: NewAttendanceNotification,
+    private val preferencesRepository: PreferencesRepository
 ) : Work {
 
     override suspend fun doWork(student: Student, semester: Semester) {
-        attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
+        attendanceRepository.getAttendance(
+            student = student,
+            semester = semester,
+            start = now().previousOrSameSchoolDay,
+            end = now().previousOrSameSchoolDay,
+            forceRefresh = true,
+            notify = preferencesRepository.isNotificationsEnable
+        )
             .waitForResult()
+
+        attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
+            .first()
+            .filterNot { it.isNotified }
+            .let {
+                if (it.isNotEmpty()) newAttendanceNotification.notify(it, student)
+
+                attendanceRepository.updateTimetable(it.onEach { attendance ->
+                    attendance.isNotified = true
+                })
+            }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
index da2dcc7f..2a5d2d7c 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt
@@ -5,8 +5,7 @@ import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.repositories.HomeworkRepository
 import io.github.wulkanowy.data.repositories.PreferencesRepository
 import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
-import io.github.wulkanowy.utils.monday
-import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.utils.nextOrSameSchoolDay
 import io.github.wulkanowy.utils.waitForResult
 import kotlinx.coroutines.flow.first
 import java.time.LocalDate.now
@@ -22,13 +21,13 @@ class HomeworkWork @Inject constructor(
         homeworkRepository.getHomework(
             student = student,
             semester = semester,
-            start = now().monday,
-            end = now().sunday,
+            start = now().nextOrSameSchoolDay,
+            end = now().nextOrSameSchoolDay,
             forceRefresh = true,
             notify = preferencesRepository.isNotificationsEnable
         ).waitForResult()
 
-        homeworkRepository.getHomeworkFromDatabase(semester, now().monday, now().sunday).first()
+        homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
             .filter { !it.isNotified }.let {
                 if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
 
diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
index 2df2c9dc..fcc33063 100644
--- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
+++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt
@@ -2,18 +2,41 @@ package io.github.wulkanowy.services.sync.works
 
 import io.github.wulkanowy.data.db.entities.Semester
 import io.github.wulkanowy.data.db.entities.Student
+import io.github.wulkanowy.data.repositories.PreferencesRepository
 import io.github.wulkanowy.data.repositories.TimetableRepository
-import io.github.wulkanowy.utils.monday
-import io.github.wulkanowy.utils.sunday
+import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
+import io.github.wulkanowy.utils.nextOrSameSchoolDay
 import io.github.wulkanowy.utils.waitForResult
+import kotlinx.coroutines.flow.first
 import java.time.LocalDate.now
 import javax.inject.Inject
 
 class TimetableWork @Inject constructor(
-    private val timetableRepository: TimetableRepository
+    private val timetableRepository: TimetableRepository,
+    private val changeTimetableNotification: ChangeTimetableNotification,
+    private val preferencesRepository: PreferencesRepository
 ) : Work {
 
     override suspend fun doWork(student: Student, semester: Semester) {
-        timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult()
+        timetableRepository.getTimetable(
+            student = student,
+            semester = semester,
+            start = now().nextOrSameSchoolDay,
+            end = now().nextOrSameSchoolDay,
+            forceRefresh = true,
+            notify = preferencesRepository.isNotificationsEnable
+        )
+            .waitForResult()
+
+        timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
+            .first()
+            .filterNot { it.isNotified }
+            .let {
+                if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
+
+                timetableRepository.updateTimetable(it.onEach { timetable ->
+                    timetable.isNotified = true
+                })
+            }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
index 0521b4a0..075557a5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt
@@ -1,14 +1,11 @@
 package io.github.wulkanowy.ui.base
 
 import android.app.ActivityManager
-import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.os.Bundle
 import android.view.View
 import android.widget.Toast
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.app.AppCompatDelegate
 import androidx.viewbinding.ViewBinding
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
@@ -40,7 +37,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
         themeManager.applyActivityTheme(this)
         super.onCreate(savedInstanceState)
         supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
-        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
 
         @Suppress("DEPRECATION")
         setTaskDescription(
@@ -83,8 +79,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
     }
 
     override fun openClearLoginView() {
-        startActivity(LoginActivity.getStartIntent(this)
-            .apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
+        startActivity(LoginActivity.getStartIntent(this))
+        finishAffinity()
     }
 
     override fun onDestroy() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt
index bd735535..6bca87f1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragmentPagerAdapter.kt
@@ -2,32 +2,33 @@ package io.github.wulkanowy.ui.base
 
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.FragmentPagerAdapter
+import androidx.lifecycle.Lifecycle
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
 
-//TODO  Use ViewPager2
-class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
-    FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+class BaseFragmentPagerAdapter(
+    private val fragmentManager: FragmentManager,
+    private val pagesCount: Int,
+    lifecycle: Lifecycle,
+) : FragmentStateAdapter(fragmentManager, lifecycle), TabLayoutMediator.TabConfigurationStrategy {
 
-    private val pages = mutableMapOf<Fragment, String?>()
+    lateinit var itemFactory: (position: Int) -> Fragment
+
+    var titleFactory: (position: Int) -> String? = { "" }
 
     var containerId = 0
 
     fun getFragmentInstance(position: Int): Fragment? {
         require(containerId != 0) { "Container id is 0" }
-        return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
+        return fragmentManager.findFragmentByTag("f$position")
     }
 
-    fun addFragments(fragments: List<Fragment>) {
-        fragments.forEach { pages[it] = null }
+    override fun createFragment(position: Int): Fragment = itemFactory(position)
+
+    override fun getItemCount() = pagesCount
+
+    override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
+        tab.text = titleFactory(position)
     }
-
-    fun addFragmentsWithTitle(pages: Map<Fragment, String>) {
-        this.pages.putAll(pages)
-    }
-
-    override fun getItem(position: Int) = pages.keys.elementAt(position)
-
-    override fun getCount() = pages.size
-
-    override fun getPageTitle(position: Int) = pages.values.elementAt(position)
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
index 6f363bfc..5cd5d010 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt
@@ -6,29 +6,27 @@ import io.github.wulkanowy.utils.flowWithResource
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
-import kotlin.coroutines.CoroutineContext
 
 open class BasePresenter<T : BaseView>(
     protected val errorHandler: ErrorHandler,
     protected val studentRepository: StudentRepository
-) : CoroutineScope {
+) {
+    private val job = SupervisorJob()
 
-    private var job = Job()
+    protected val presenterScope = CoroutineScope(job + Dispatchers.Main)
 
-    private val jobs = mutableMapOf<String, Job>()
-
-    override val coroutineContext: CoroutineContext
-        get() = Dispatchers.Main + job
+    private val childrenJobs = mutableMapOf<String, Job>()
 
     var view: T? = null
 
     open fun onAttachView(view: T) {
-        job = Job()
         this.view = view
         errorHandler.apply {
             showErrorMessage = view::showError
@@ -64,22 +62,22 @@ open class BasePresenter<T : BaseView>(
     }
 
     fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
-        jobs[individualJobTag]?.cancel()
-        val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
-        jobs[individualJobTag] = job
+        childrenJobs[individualJobTag]?.cancel()
+        val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope)
+        childrenJobs[individualJobTag] = job
         Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
         return job
     }
 
     fun cancelJobs(vararg names: String) {
         names.forEach {
-            jobs[it]?.cancel()
+            childrenJobs[it]?.cancel()
         }
     }
 
     open fun onDetachView() {
-        view = null
-        job.cancel()
+        job.cancelChildren()
         errorHandler.clear()
+        view = null
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
index 4ce97770..4c279d81 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt
@@ -11,6 +11,7 @@ import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.appcompat.app.AlertDialog
 import androidx.core.content.getSystemService
+import androidx.core.view.isGone
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.databinding.DialogErrorBinding
@@ -24,8 +25,6 @@ import io.github.wulkanowy.utils.openEmailClient
 import io.github.wulkanowy.utils.openInternetBrowser
 import okhttp3.internal.http2.StreamResetException
 import java.io.InterruptedIOException
-import java.io.PrintWriter
-import java.io.StringWriter
 import java.net.ConnectException
 import java.net.SocketTimeoutException
 import java.net.UnknownHostException
@@ -64,26 +63,26 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        val stringWriter = StringWriter().apply {
-            error.printStackTrace(PrintWriter(this))
-        }
+        val errorStacktrace = error.stackTraceToString()
 
         with(binding) {
-            errorDialogContent.text = stringWriter.toString()
+            errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
             with(errorDialogHorizontalScroll) {
                 post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
             }
             errorDialogCopy.setOnClickListener {
-                val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
+                val clip = ClipData.newPlainText("Error details", errorStacktrace)
                 activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
 
                 Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
             }
             errorDialogCancel.setOnClickListener { dismiss() }
             errorDialogReport.setOnClickListener {
-                openConfirmDialog { openEmailClient(stringWriter.toString()) }
+                openConfirmDialog { openEmailClient(errorStacktrace) }
             }
-            errorDialogMessage.text = resources.getString(error)
+            errorDialogHumanizedMessage.text = resources.getString(error)
+            errorDialogErrorMessage.text = error.localizedMessage
+            errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
             errorDialogReport.isEnabled = when (error) {
                 is UnknownHostException,
                 is InterruptedIOException,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
index 7c32ef18..fbc994e2 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt
@@ -1,6 +1,7 @@
 package io.github.wulkanowy.ui.base
 
-import android.content.res.Resources
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
 import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
 import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
@@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
 import timber.log.Timber
 import javax.inject.Inject
 
-open class ErrorHandler @Inject constructor(protected val resources: Resources) {
+open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) {
 
     var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
 
@@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
     }
 
     protected open fun proceed(error: Throwable) {
-        showErrorMessage(resources.getString(error), error)
+        showErrorMessage(context.resources.getString(error), error)
         when (error) {
             is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
             is ScramblerException, is BadCredentialsException -> onSessionExpired()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
index b560ed2e..e934a182 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt
@@ -41,14 +41,15 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
         )
     }
 
-    private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
-        return activity.packageManager
+    private fun isThemeApplicable(activity: AppCompatActivity) =
+        activity.packageManager
             .getPackageInfo(activity.packageName, GET_ACTIVITIES)
-            .activities.singleOrNull { it.name == activity::class.java.canonicalName }
-            ?.theme.let {
+            .activities
+            .singleOrNull { it.name == activity::class.java.canonicalName }
+            ?.theme
+            .let {
                 it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
                     || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
                     || it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
             }
-    }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt
new file mode 100644
index 00000000..43d4b5f9
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt
@@ -0,0 +1,136 @@
+package io.github.wulkanowy.ui.modules
+
+import androidx.fragment.app.Fragment
+import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
+import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
+import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
+import io.github.wulkanowy.ui.modules.exam.ExamFragment
+import io.github.wulkanowy.ui.modules.grade.GradeFragment
+import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
+import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
+import io.github.wulkanowy.ui.modules.message.MessageFragment
+import io.github.wulkanowy.ui.modules.more.MoreFragment
+import io.github.wulkanowy.ui.modules.note.NoteFragment
+import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
+import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
+import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
+import java.io.Serializable
+import java.time.LocalDate
+
+sealed interface Destination : Serializable {
+
+    /*
+    Type in children classes have to be as getter to avoid null in enums
+    https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
+    */
+    val type: Type
+
+    val fragment: Fragment
+
+    enum class Type(val defaultDestination: Destination) {
+        DASHBOARD(Dashboard),
+        GRADE(Grade),
+        ATTENDANCE(Attendance),
+        EXAM(Exam),
+        TIMETABLE(Timetable()),
+        HOMEWORK(Homework),
+        NOTE(Note),
+        CONFERENCE(Conference),
+        SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
+        SCHOOL(School),
+        LUCKY_NUMBER(More),
+        MORE(More),
+        MESSAGE(Message);
+    }
+
+    object Dashboard : Destination {
+
+        override val type get() = Type.DASHBOARD
+
+        override val fragment get() = DashboardFragment.newInstance()
+    }
+
+    object Grade : Destination {
+
+        override val type get() = Type.GRADE
+
+        override val fragment get() = GradeFragment.newInstance()
+    }
+
+    object Attendance : Destination {
+
+        override val type get() = Type.ATTENDANCE
+
+        override val fragment get() = AttendanceFragment.newInstance()
+    }
+
+    object Exam : Destination {
+
+        override val type get() = Type.EXAM
+
+        override val fragment get() = ExamFragment.newInstance()
+    }
+
+    data class Timetable(val date: LocalDate? = null) : Destination {
+
+        override val type get() = Type.TIMETABLE
+
+        override val fragment get() = TimetableFragment.newInstance(date)
+    }
+
+    object Homework : Destination {
+
+        override val type get() = Type.HOMEWORK
+
+        override val fragment get() = HomeworkFragment.newInstance()
+    }
+
+    object Note : Destination {
+
+        override val type get() = Type.NOTE
+
+        override val fragment get() = NoteFragment.newInstance()
+    }
+
+    object Conference : Destination {
+
+        override val type get() = Type.CONFERENCE
+
+        override val fragment get() = ConferenceFragment.newInstance()
+    }
+
+    object SchoolAnnouncement : Destination {
+
+        override val type get() = Type.SCHOOL_ANNOUNCEMENT
+
+        override val fragment get() = SchoolAnnouncementFragment.newInstance()
+    }
+
+    object School : Destination {
+
+        override val type get() = Type.SCHOOL
+
+        override val fragment get() = SchoolFragment.newInstance()
+    }
+
+    object LuckyNumber : Destination {
+
+        override val type get() = Type.LUCKY_NUMBER
+
+        override val fragment get() = LuckyNumberFragment.newInstance()
+    }
+
+    object More : Destination {
+
+        override val type get() = Type.MORE
+
+        override val fragment get() = MoreFragment.newInstance()
+    }
+
+    object Message : Destination {
+
+        override val type get() = Type.MESSAGE
+
+        override val fragment get() = MessageFragment.newInstance()
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt
index 6bcf5f77..55274934 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt
@@ -82,18 +82,20 @@ class AboutPresenter @Inject constructor(
 
     private fun loadData() {
         view?.run {
-            updateData(listOfNotNull(
-                versionRes,
-                creatorsRes,
-                feedbackRes,
-                faqRes,
-                discordRes,
-                facebookRes,
-                twitterRes,
-                homepageRes,
-                licensesRes,
-                privacyRes
-            ))
+            updateData(
+                listOfNotNull(
+                    versionRes,
+                    creatorsRes,
+                    feedbackRes,
+                    faqRes,
+                    discordRes,
+                    facebookRes,
+                    twitterRes,
+                    homepageRes,
+                    licensesRes,
+                    privacyRes
+                )
+            )
         }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt
index cc430fc2..5368cc19 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt
@@ -31,7 +31,7 @@ class LicensePresenter @Inject constructor(
 
     private fun loadData() {
         flowWithResource {
-            withContext(dispatchers.backgroundThread) {
+            withContext(dispatchers.io) {
                 view?.appLibraries.orEmpty()
             }
         }.onEach {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt
index ab6eec41..66e39fc7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditColorAdapter.kt
@@ -3,12 +3,9 @@ package io.github.wulkanowy.ui.modules.account.accountedit
 import android.annotation.SuppressLint
 import android.content.res.ColorStateList
 import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.RippleDrawable
-import android.graphics.drawable.StateListDrawable
-import android.os.Build
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import androidx.core.view.isVisible
@@ -52,30 +49,13 @@ class AccountEditColorAdapter @Inject constructor() :
         }
     }
 
-    private fun Int.createForegroundDrawable(): Drawable =
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            val mask = GradientDrawable().apply {
-                shape = GradientDrawable.OVAL
-                setColor(Color.BLACK)
-            }
-            RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
-        } else {
-            val foreground = StateListDrawable().apply {
-                alpha = 80
-                setEnterFadeDuration(250)
-                setExitFadeDuration(250)
-            }
-
-            val mask = GradientDrawable().apply {
-                shape = GradientDrawable.OVAL
-                setColor(this@createForegroundDrawable.rippleColor)
-            }
-
-            foreground.apply {
-                addState(intArrayOf(android.R.attr.state_pressed), mask)
-                addState(intArrayOf(), ColorDrawable(Color.TRANSPARENT))
-            }
+    private fun Int.createForegroundDrawable(): Drawable {
+        val mask = GradientDrawable().apply {
+            shape = GradientDrawable.OVAL
+            setColor(Color.BLACK)
         }
+        return RippleDrawable(ColorStateList.valueOf(this.rippleColor), null, mask)
+    }
 
     private inline val Int.rippleColor: Int
         get() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
index 6cee2396..5d5ed504 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt
@@ -9,7 +9,7 @@ import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Attendance
 import io.github.wulkanowy.data.enums.SentExcuseStatus
 import io.github.wulkanowy.databinding.ItemAttendanceBinding
-import io.github.wulkanowy.utils.description
+import io.github.wulkanowy.utils.descriptionRes
 import io.github.wulkanowy.utils.isExcusableOrNotExcused
 import javax.inject.Inject
 
@@ -36,7 +36,7 @@ class AttendanceAdapter @Inject constructor() :
         with(holder.binding) {
             attendanceItemNumber.text = item.number.toString()
             attendanceItemSubject.text = item.subject
-            attendanceItemDescription.setText(item.description)
+            attendanceItemDescription.setText(item.descriptionRes)
             attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
             attendanceItemNumber.visibility = View.GONE
             attendanceItemExcuseInfo.visibility = View.GONE
@@ -46,7 +46,7 @@ class AttendanceAdapter @Inject constructor() :
                 onExcuseCheckboxSelect(item, checked)
             }
 
-            when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) {
+            when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it)}) {
                 SentExcuseStatus.WAITING -> {
                     attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
                     attendanceItemExcuseInfo.visibility = View.VISIBLE
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
index d816d8f0..9b5c63e4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt
@@ -7,7 +7,7 @@ import android.view.ViewGroup
 import androidx.fragment.app.DialogFragment
 import io.github.wulkanowy.data.db.entities.Attendance
 import io.github.wulkanowy.databinding.DialogAttendanceBinding
-import io.github.wulkanowy.utils.description
+import io.github.wulkanowy.utils.descriptionRes
 import io.github.wulkanowy.utils.lifecycleAwareVariable
 import io.github.wulkanowy.utils.toFormattedString
 
@@ -45,7 +45,7 @@ class AttendanceDialog : DialogFragment() {
 
         with(binding) {
             attendanceDialogSubjectValue.text = attendance.subject
-            attendanceDialogDescriptionValue.setText(attendance.description)
+            attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
             attendanceDialogDateValue.text = attendance.date.toFormattedString()
             attendanceDialogNumberValue.text = attendance.number.toString()
             attendanceDialogClose.setOnClickListener { dismiss() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
index 3fbdaec5..bd9a6692 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt
@@ -12,6 +12,7 @@ import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.view.ActionMode
+import androidx.core.view.isVisible
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.datepicker.CalendarConstraints
 import com.google.android.material.datepicker.MaterialDatePicker
@@ -121,9 +122,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
             attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
             attendanceSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
             attendanceSwipe.setProgressBackgroundColorSchemeColor(
-                requireContext().getThemeAttrColor(
-                    R.attr.colorSwipeRefresh
-                )
+                requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
             )
             attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
             attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@@ -134,7 +133,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
 
             attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
 
-            attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            attendanceNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
@@ -218,7 +217,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
     }
 
     override fun showExcuseButton(show: Boolean) {
-        binding.attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
+        binding.attendanceExcuseButton.isVisible = show
     }
 
     override fun showAttendanceDialog(lesson: Attendance) {
@@ -289,12 +288,16 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
     }
 
     override fun showExcuseCheckboxes(show: Boolean) {
-        attendanceAdapter.apply {
+        with(attendanceAdapter) {
             excuseActionMode = show
             notifyDataSetChanged()
         }
     }
 
+    override fun showDayNavigation(show: Boolean) {
+        binding.attendanceNavContainer.isVisible = show
+    }
+
     override fun finishActionMode() {
         actionMode?.finish()
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
index 03545b25..54d29bcf 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt
@@ -174,6 +174,8 @@ class AttendancePresenter @Inject constructor(
         view?.apply {
             showExcuseCheckboxes(true)
             showExcuseButton(false)
+            enableSwipe(false)
+            showDayNavigation(false)
         }
         attendanceToExcuseList.clear()
         return true
@@ -183,6 +185,8 @@ class AttendancePresenter @Inject constructor(
         view?.apply {
             showExcuseCheckboxes(false)
             showExcuseButton(true)
+            enableSwipe(true)
+            showDayNavigation(true)
         }
     }
 
@@ -259,9 +263,8 @@ class AttendancePresenter @Inject constructor(
                         showEmpty(filteredAttendance.isEmpty())
                         showErrorView(false)
                         showContent(filteredAttendance.isNotEmpty())
-                        showExcuseButton(filteredAttendance.any { item ->
-                            (!isParent && isVulcanExcusedFunctionEnabled) || (isParent && item.isExcusableOrNotExcused)
-                        })
+                        val anyExcusables = filteredAttendance.any { it.isExcusableOrNotExcused }
+                        showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
                     }
                     analytics.logEvent(
                         "load_data",
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
index 738f2ff8..7ddd75f4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt
@@ -60,6 +60,8 @@ interface AttendanceView : BaseView {
 
     fun showExcuseCheckboxes(show: Boolean)
 
+    fun showDayNavigation(show: Boolean)
+
     fun finishActionMode()
 
     fun popView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
index 118971e6..dd1644a9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt
@@ -71,7 +71,7 @@ class AttendanceSummaryFragment :
             setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
         }
 
-        binding.attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
+        binding.attendanceSummarySubjectsContainer.elevation = requireContext().dpToPx(1f)
     }
 
     override fun updateSubjects(data: ArrayList<String>) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt
index 11b575c1..440bbd5d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardAdapter.kt
@@ -1,6 +1,8 @@
 package io.github.wulkanowy.ui.modules.dashboard
 
 import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.graphics.Typeface
 import android.os.Handler
 import android.os.Looper
@@ -18,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.db.entities.Timetable
 import io.github.wulkanowy.data.db.entities.TimetableHeader
 import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
+import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
 import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
 import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
 import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
@@ -63,6 +66,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
 
     var onConferencesTileClickListener: () -> Unit = {}
 
+    var onAdminMessageClickListener: (String?) -> Unit = {}
+
     val items = mutableListOf<DashboardItem>()
 
     fun submitList(newItems: List<DashboardItem>) {
@@ -109,6 +114,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
             DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
                 ItemDashboardConferencesBinding.inflate(inflater, parent, false)
             )
+            DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
+                ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
+            )
             else -> throw IllegalArgumentException()
         }
     }
@@ -123,6 +131,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
             is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
             is ExamsViewHolder -> bindExamsViewHolder(holder, position)
             is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
+            is AdminMessageViewHolder -> bindAdminMessage(holder, position)
         }
     }
 
@@ -290,7 +299,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
             val currentDayHeader =
                 timetableFull?.headers.orEmpty().singleOrNull { it.date == currentDate }
 
-            val tomorrowTimetable = timetableFull?.lessons.orEmpty()
+            val tomorrowTimetable = timetableFull?.lessons
+                .orEmpty()
                 .filter { it.date == currentDate.plusDays(1) }
                 .filterNot { it.canceled }
             val tomorrowDayHeader =
@@ -301,26 +311,31 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
                     dateToNavigate = currentDate
                     updateLessonView(item, currentTimetable, binding)
                     binding.dashboardLessonsItemTitleTomorrow.isVisible = false
+                    binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
                 }
                 tomorrowTimetable.isNotEmpty() -> {
                     dateToNavigate = tomorrowDate
                     updateLessonView(item, tomorrowTimetable, binding)
                     binding.dashboardLessonsItemTitleTomorrow.isVisible = true
+                    binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
                 }
                 currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
                     dateToNavigate = currentDate
                     updateLessonView(item, emptyList(), binding, currentDayHeader)
                     binding.dashboardLessonsItemTitleTomorrow.isVisible = false
+                    binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
                 }
                 tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
                     dateToNavigate = tomorrowDate
                     updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
                     binding.dashboardLessonsItemTitleTomorrow.isVisible = true
+                    binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible = false
                 }
                 else -> {
-                    dateToNavigate = tomorrowDate
+                    dateToNavigate = currentDate
                     updateLessonView(item, emptyList(), binding)
-                    binding.dashboardLessonsItemTitleTomorrow.isVisible =
+                    binding.dashboardLessonsItemTitleTomorrow.isVisible = false
+                    binding.dashboardLessonsItemTitleTodayAndTomorrow.isVisible =
                         !(item.isLoading && item.error == null)
                 }
             }
@@ -692,6 +707,34 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
         }
     }
 
+    private fun bindAdminMessage(adminMessageViewHolder: AdminMessageViewHolder, position: Int) {
+        val item = (items[position] as DashboardItem.AdminMessages).adminMessage ?: return
+        val context = adminMessageViewHolder.binding.root.context
+        val (backgroundColor, textColor) = when (item.priority) {
+            "HIGH" -> {
+                context.getThemeAttrColor(R.attr.colorPrimary) to
+                    context.getThemeAttrColor(R.attr.colorOnPrimary)
+            }
+            "MEDIUM" -> {
+                context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
+            }
+            else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
+        }
+
+        with(adminMessageViewHolder.binding) {
+            dashboardAdminMessageItemTitle.text = item.title
+            dashboardAdminMessageItemTitle.setTextColor(textColor)
+            dashboardAdminMessageItemDescription.text = item.content
+            dashboardAdminMessageItemDescription.setTextColor(textColor)
+            dashboardAdminMessageItemIcon.setColorFilter(textColor)
+
+            root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
+            item.destinationUrl?.let { url ->
+                root.setOnClickListener { onAdminMessageClickListener(url) }
+            }
+        }
+    }
+
     class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
         RecyclerView.ViewHolder(binding.root)
 
@@ -731,6 +774,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
         val adapter by lazy { DashboardConferencesAdapter() }
     }
 
+    class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
+        RecyclerView.ViewHolder(binding.root)
+
     private class DiffCallback(
         private val newList: List<DashboardItem>,
         private val oldList: List<DashboardItem>
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
index 096c1b77..775b7b55 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt
@@ -10,6 +10,7 @@ import androidx.core.view.isVisible
 import androidx.recyclerview.widget.DefaultItemAnimator
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.databinding.FragmentDashboardBinding
@@ -29,6 +30,7 @@ import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragm
 import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
 import io.github.wulkanowy.utils.capitalise
 import io.github.wulkanowy.utils.getThemeAttrColor
+import io.github.wulkanowy.utils.openInternetBrowser
 import io.github.wulkanowy.utils.toFormattedString
 import java.time.LocalDate
 import javax.inject.Inject
@@ -97,6 +99,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
             onConferencesTileClickListener = {
                 mainActivity.pushView(ConferenceFragment.newInstance())
             }
+            onAdminMessageClickListener = presenter::onAdminMessageSelected
+
+            registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+                override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+                    binding.dashboardRecycler.scrollToPosition(0)
+                }
+            })
         }
 
         with(binding) {
@@ -188,6 +197,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
         (requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
     }
 
+    override fun openInternetBrowser(url: String) {
+        requireContext().openInternetBrowser(url)
+    }
+
     override fun onDestroyView() {
         dashboardAdapter.clearTimers()
         presenter.onDetachView()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
index cf99f0c9..92665857 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt
@@ -1,5 +1,6 @@
 package io.github.wulkanowy.ui.modules.dashboard
 
+import io.github.wulkanowy.data.db.entities.AdminMessage
 import io.github.wulkanowy.data.db.entities.Conference
 import io.github.wulkanowy.data.db.entities.Exam
 import io.github.wulkanowy.data.db.entities.Grade
@@ -16,6 +17,15 @@ sealed class DashboardItem(val type: Type) {
 
     abstract val isDataLoaded: Boolean
 
+    data class AdminMessages(
+        val adminMessage: AdminMessage? = null,
+        override val error: Throwable? = null,
+        override val isLoading: Boolean = false
+    ) : DashboardItem(Type.ADMIN_MESSAGE) {
+
+        override val isDataLoaded get() = adminMessage != null
+    }
+
     data class Account(
         val student: Student? = null,
         override val error: Throwable? = null,
@@ -96,6 +106,7 @@ sealed class DashboardItem(val type: Type) {
     }
 
     enum class Type {
+        ADMIN_MESSAGE,
         ACCOUNT,
         HORIZONTAL_GROUP,
         LESSONS,
@@ -108,6 +119,7 @@ sealed class DashboardItem(val type: Type) {
     }
 
     enum class Tile {
+        ADMIN_MESSAGE,
         ACCOUNT,
         LUCKY_NUMBER,
         MESSAGES,
@@ -123,6 +135,7 @@ sealed class DashboardItem(val type: Type) {
 }
 
 fun DashboardItem.Tile.toDashboardItemType() = when (this) {
+    DashboardItem.Tile.ADMIN_MESSAGE -> DashboardItem.Type.ADMIN_MESSAGE
     DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
     DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
     DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt
index cf4097a4..b9625570 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItemMoveCallback.kt
@@ -21,7 +21,7 @@ class DashboardItemMoveCallback(
         recyclerView: RecyclerView,
         viewHolder: RecyclerView.ViewHolder
     ): Int {
-        val dragFlags = if (viewHolder.bindingAdapterPosition != 0) {
+        val dragFlags = if (!viewHolder.isAdminMessageOrAccountItem) {
             ItemTouchHelper.UP or ItemTouchHelper.DOWN
         } else 0
 
@@ -32,7 +32,7 @@ class DashboardItemMoveCallback(
         recyclerView: RecyclerView,
         current: RecyclerView.ViewHolder,
         target: RecyclerView.ViewHolder
-    ) = target.bindingAdapterPosition != 0
+    ) = !target.isAdminMessageOrAccountItem
 
     override fun onMove(
         recyclerView: RecyclerView,
@@ -52,4 +52,7 @@ class DashboardItemMoveCallback(
 
         onUserInteractionEndListener(dashboardAdapter.items.toList())
     }
+
+    private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean
+        get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
index c513a9f6..2ec198be 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
@@ -5,6 +5,7 @@ import io.github.wulkanowy.data.Status
 import io.github.wulkanowy.data.db.entities.LuckyNumber
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.enums.MessageFolder
+import io.github.wulkanowy.data.repositories.AdminMessageRepository
 import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
 import io.github.wulkanowy.data.repositories.ConferenceRepository
 import io.github.wulkanowy.data.repositories.ExamRepository
@@ -50,7 +51,8 @@ class DashboardPresenter @Inject constructor(
     private val examRepository: ExamRepository,
     private val conferenceRepository: ConferenceRepository,
     private val preferencesRepository: PreferencesRepository,
-    private val schoolAnnouncementRepository: SchoolAnnouncementRepository
+    private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
+    private val adminMessageRepository: AdminMessageRepository
 ) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
 
     private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
@@ -149,7 +151,7 @@ class DashboardPresenter @Inject constructor(
         tileList: List<DashboardItem.Type>,
         forceRefresh: Boolean
     ) {
-        launch {
+        presenterScope.launch {
             Timber.i("Loading dashboard account data started")
             val student = runCatching { studentRepository.getCurrentStudent(true) }
                 .onFailure {
@@ -179,6 +181,7 @@ class DashboardPresenter @Inject constructor(
                         loadConferences(student, forceRefresh)
                     }
                     DashboardItem.Type.ADS -> TODO()
+                    DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
                 }
             }
         }
@@ -225,6 +228,10 @@ class DashboardPresenter @Inject constructor(
         }.toSet()
     }
 
+    fun onAdminMessageSelected(url: String?) {
+        url?.let { view?.openInternetBrowser(it) }
+    }
+
     private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
         flow {
             val semester = semesterRepository.getCurrentSemester(student)
@@ -309,18 +316,17 @@ class DashboardPresenter @Inject constructor(
 
             gradeRepository.getGrades(student, semester, forceRefresh)
         }.map { originalResource ->
-            val filteredSubjectWithGrades = originalResource.data?.first.orEmpty()
-                .filter { grade ->
-                    grade.date.isAfter(LocalDate.now().minusDays(7))
-                }
-                .groupBy { grade -> grade.subject }
+            val filteredSubjectWithGrades = originalResource.data?.first
+                .orEmpty()
+                .filter { it.date >= LocalDate.now().minusDays(7) }
+                .groupBy { it.subject }
                 .mapValues { entry ->
                     entry.value
                         .take(5)
-                        .sortedBy { grade -> grade.date }
+                        .sortedByDescending { it.date }
                 }
                 .toList()
-                .sortedBy { subjectWithGrades -> subjectWithGrades.second[0].date }
+                .sortedByDescending { (_, grades) -> grades[0].date }
                 .toMap()
 
             Resource(
@@ -424,9 +430,9 @@ class DashboardPresenter @Inject constructor(
         }.map { homeworkResource ->
             val currentDate = LocalDate.now()
 
-            val filteredHomework = homeworkResource.data?.filter {
-                (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone
-            }
+            val filteredHomework = homeworkResource.data
+                ?.filter { (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone }
+                ?.sortedBy { it.date }
 
             homeworkResource.copy(data = filteredHomework)
         }.onEach {
@@ -567,6 +573,38 @@ class DashboardPresenter @Inject constructor(
         }.launch("dashboard_conferences")
     }
 
+    private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
+        flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) }
+            .onEach {
+                when (it.status) {
+                    Status.LOADING -> {
+                        Timber.i("Loading dashboard admin message data started")
+                        if (forceRefresh) return@onEach
+                        updateData(DashboardItem.AdminMessages(), forceRefresh)
+                    }
+                    Status.SUCCESS -> {
+                        Timber.i("Loading dashboard admin message result: Success")
+                        updateData(
+                            dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data),
+                            forceRefresh = forceRefresh
+                        )
+                    }
+                    Status.ERROR -> {
+                        Timber.i("Loading dashboard admin message result: An exception occurred")
+                        errorHandler.dispatch(it.error!!)
+                        updateData(
+                            dashboardItem = DashboardItem.AdminMessages(
+                                adminMessage = it.data,
+                                error = it.error
+                            ),
+                            forceRefresh = forceRefresh
+                        )
+                    }
+                }
+            }
+            .launch("dashboard_admin_messages")
+    }
+
     private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
         val isForceRefreshError = forceRefresh && dashboardItem.error != null
         val isFirstRunDataLoadedError =
@@ -579,6 +617,13 @@ class DashboardPresenter @Inject constructor(
 
         sortDashboardItems()
 
+        if (dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded) {
+            dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE
+            dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADMIN_MESSAGE
+
+            dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE }
+        }
+
         if (forceRefresh) {
             updateForceRefreshData(dashboardItem)
         } else {
@@ -610,9 +655,12 @@ class DashboardPresenter @Inject constructor(
     }
 
     private fun updateForceRefreshData(dashboardItem: DashboardItem) {
+        val isNotLoadedAdminMessage =
+            dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded
+
         with(dashboardItemRefreshLoadedList) {
             removeAll { it.type == dashboardItem.type }
-            add(dashboardItem)
+            if (!isNotLoadedAdminMessage) add(dashboardItem)
         }
 
         val isRefreshItemLoaded =
@@ -644,7 +692,9 @@ class DashboardPresenter @Inject constructor(
         itemsLoadedList: List<DashboardItem>,
         forceRefresh: Boolean
     ) {
-        val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
+        val filteredItems = itemsLoadedList.filterNot {
+            it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
+        }
         val isAccountItemError =
             itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
         val isGeneralError =
@@ -676,10 +726,13 @@ class DashboardPresenter @Inject constructor(
         val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
 
         dashboardItemLoadedList.sortBy { tile ->
-            dashboardItemsPosition?.getOrDefault(
-                tile.type,
+            val defaultPosition = if (tile is DashboardItem.AdminMessages) {
+                -1
+            } else {
                 tile.type.ordinal + 100
-            ) ?: tile.type.ordinal
+            }
+
+            dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal
         }
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
index 899eb320..730e19a3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt
@@ -25,4 +25,6 @@ interface DashboardView : BaseView {
     fun popViewToRoot()
 
     fun openNotificationsCenterView()
+
+    fun openInternetBrowser(url: String)
 }
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
index 6103317d..d0dfcd69 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt
@@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.debug.notification
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
+import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
 import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification
 import io.github.wulkanowy.services.sync.notifications.NewExamNotification
 import io.github.wulkanowy.services.sync.notifications.NewGradeNotification
@@ -13,6 +15,7 @@ import io.github.wulkanowy.services.sync.notifications.NewNoteNotification
 import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
 import io.github.wulkanowy.ui.base.BasePresenter
 import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
@@ -22,6 +25,7 @@ import io.github.wulkanowy.ui.modules.debug.notification.mock.debugLuckyNumber
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugMessageItems
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugNoteItems
 import io.github.wulkanowy.ui.modules.debug.notification.mock.debugSchoolAnnouncementItems
+import io.github.wulkanowy.ui.modules.debug.notification.mock.debugTimetableItems
 import kotlinx.coroutines.launch
 import timber.log.Timber
 import javax.inject.Inject
@@ -37,6 +41,8 @@ class NotificationDebugPresenter @Inject constructor(
     private val newNoteNotification: NewNoteNotification,
     private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification,
     private val newLuckyNumberNotification: NewLuckyNumberNotification,
+    private val changeTimetableNotification: ChangeTimetableNotification,
+    private val newAttendanceNotification: NewAttendanceNotification,
 ) : BasePresenter<NotificationDebugView>(errorHandler, studentRepository) {
 
     private val items = listOf(
@@ -64,6 +70,12 @@ class NotificationDebugPresenter @Inject constructor(
         NotificationDebugItem(R.string.note_title) { n ->
             withStudent { newNoteNotification.notify(debugNoteItems.take(n), it) }
         },
+        NotificationDebugItem(R.string.attendance_title) { n ->
+            withStudent { newAttendanceNotification.notify(debugAttendanceItems.take(n), it) }
+        },
+        NotificationDebugItem(R.string.timetable_title) { n ->
+            withStudent { changeTimetableNotification.notify(debugTimetableItems.take(n), it) }
+        },
         NotificationDebugItem(R.string.school_announcement_title) { n ->
             withStudent {
                 newSchoolAnnouncementNotification.notify(debugSchoolAnnouncementItems.take(n), it)
@@ -88,7 +100,7 @@ class NotificationDebugPresenter @Inject constructor(
     }
 
     private fun withStudent(block: suspend (Student) -> Unit) {
-        launch {
+        presenterScope.launch {
             block(studentRepository.getCurrentStudent(false))
         }
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt
new file mode 100644
index 00000000..042cf07e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt
@@ -0,0 +1,35 @@
+package io.github.wulkanowy.ui.modules.debug.notification.mock
+
+import io.github.wulkanowy.data.db.entities.Attendance
+import java.time.LocalDate
+
+val debugAttendanceItems = listOf(
+    generateAttendance("Matematyka", "PRESENCE"),
+    generateAttendance("Język angielski", "UNEXCUSED_LATENESS"),
+    generateAttendance("Geografia", "ABSENCE_UNEXCUSED"),
+    generateAttendance("Sieci komputerowe", "ABSENCE_EXCUSED"),
+    generateAttendance("Systemy operacyjne", "EXCUSED_LATENESS"),
+    generateAttendance("Język niemiecki", "ABSENCE_UNEXCUSED"),
+    generateAttendance("Biologia", "ABSENCE_UNEXCUSED"),
+    generateAttendance("Chemia", "ABSENCE_EXCUSED"),
+    generateAttendance("Fizyka", "ABSENCE_UNEXCUSED"),
+    generateAttendance("Matematyka", "ABSENCE_EXCUSED"),
+)
+
+private fun generateAttendance(subject: String, name: String) = Attendance(
+    subject = subject,
+    studentId = 0,
+    diaryId = 0,
+    date = LocalDate.now(),
+    timeId = 0,
+    number = 1,
+    name = name,
+    presence = false,
+    absence = false,
+    exemption = false,
+    lateness = false,
+    excused = false,
+    deleted = false,
+    excusable = false,
+    excuseStatus = ""
+)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt
new file mode 100644
index 00000000..428c001d
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt
@@ -0,0 +1,39 @@
+package io.github.wulkanowy.ui.modules.debug.notification.mock
+
+import io.github.wulkanowy.data.db.entities.Timetable
+import java.time.LocalDate
+import java.time.LocalDateTime
+import kotlin.random.Random
+
+val debugTimetableItems = listOf(
+    generateTimetable("Matematyka", "12", "01"),
+    generateTimetable("Język angielski", "23", "12"),
+    generateTimetable("Geografia", "34", "23"),
+    generateTimetable("Sieci komputerowe", "45", "34"),
+    generateTimetable("Systemy operacyjne", "56", "45"),
+    generateTimetable("Język niemiecki", "67", "56"),
+    generateTimetable("Biologia", "78", "67"),
+    generateTimetable("Chemia", "89", "78"),
+    generateTimetable("Fizyka", "90", "89"),
+    generateTimetable("Matematyka", "01", "90"),
+)
+
+private fun generateTimetable(subject: String, room: String, roomOld: String) = Timetable(
+    subject = subject,
+    studentId = 0,
+    diaryId = 0,
+    date = LocalDate.now().minusDays(Random.nextLong(0, 8)),
+    number = 1,
+    start = LocalDateTime.now().plusHours(1),
+    end = LocalDateTime.now(),
+    subjectOld = "",
+    group = "",
+    room = room,
+    roomOld = roomOld,
+    teacher = "Wtorkowska Renata",
+    teacherOld = "",
+    info = "",
+    isStudentPlan = true,
+    changes = true,
+    canceled = true
+)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
index fb7939bc..ddd0e4a1 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt
@@ -64,7 +64,7 @@ class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam),
             examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
             examNextButton.setOnClickListener { presenter.onNextWeek() }
 
-            examNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            examNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeExpandMode.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeExpandMode.kt
new file mode 100644
index 00000000..722e986e
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeExpandMode.kt
@@ -0,0 +1,9 @@
+package io.github.wulkanowy.ui.modules.grade
+
+enum class GradeExpandMode(val value: String) {
+    ONE("one"), UNLIMITED("any"), ALWAYS_EXPANDED("always");
+
+    companion object {
+        fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ONE
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
index b3ef3037..0a8561ee 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt
@@ -8,6 +8,7 @@ import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import androidx.appcompat.app.AlertDialog
+import com.google.android.material.tabs.TabLayoutMediator
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Semester
@@ -29,7 +30,13 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
     @Inject
     lateinit var presenter: GradePresenter
 
-    private val pagerAdapter by lazy { BaseFragmentPagerAdapter(childFragmentManager) }
+    private val pagerAdapter by lazy {
+        BaseFragmentPagerAdapter(
+            fragmentManager = childFragmentManager,
+            pagesCount = 3,
+            lifecycle = lifecycle,
+        )
+    }
 
     private var semesterSwitchMenu: MenuItem? = null
 
@@ -62,28 +69,35 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
     }
 
     override fun initView() {
-        with(pagerAdapter) {
-            containerId = binding.gradeViewPager.id
-            addFragmentsWithTitle(
-                mapOf(
-                    GradeDetailsFragment.newInstance() to getString(R.string.all_details),
-                    GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
-                    GradeStatisticsFragment.newInstance() to getString(R.string.grade_menu_statistics)
-                )
-            )
-        }
-
         with(binding.gradeViewPager) {
             adapter = pagerAdapter
             offscreenPageLimit = 3
             setOnSelectPageListener(presenter::onPageSelected)
         }
 
-        with(binding.gradeTabLayout) {
-            setupWithViewPager(binding.gradeViewPager)
-            setElevationCompat(context.dpToPx(4f))
+        with(pagerAdapter) {
+            containerId = binding.gradeViewPager.id
+            titleFactory = {
+                when (it) {
+                    0 -> getString(R.string.all_details)
+                    1 -> getString(R.string.grade_menu_summary)
+                    2 -> getString(R.string.grade_menu_statistics)
+                    else -> throw IllegalStateException()
+                }
+            }
+            itemFactory = {
+                when (it) {
+                    0 -> GradeDetailsFragment.newInstance()
+                    1 -> GradeSummaryFragment.newInstance()
+                    2 -> GradeStatisticsFragment.newInstance()
+                    else -> throw IllegalStateException()
+                }
+            }
+            TabLayoutMediator(binding.gradeTabLayout, binding.gradeViewPager, this).attach()
         }
 
+        binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
+
         with(binding) {
             gradeErrorRetry.setOnClickListener { presenter.onRetry() }
             gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
index 504c730d..76e88bcd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt
@@ -101,7 +101,6 @@ class GradePresenter @Inject constructor(
     private fun loadData() {
         flowWithResource {
             val student = studentRepository.getCurrentStudent()
-            delay(200)
             semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
         }.onEach {
             when (it.status) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
index 01631140..d96ac092 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt
@@ -5,6 +5,7 @@ import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import androidx.core.view.isVisible
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.NO_POSITION
@@ -13,9 +14,11 @@ import io.github.wulkanowy.data.db.entities.Grade
 import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
 import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
 import io.github.wulkanowy.ui.base.BaseExpandableAdapter
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
 import io.github.wulkanowy.utils.getBackgroundColor
 import io.github.wulkanowy.utils.toFormattedString
 import timber.log.Timber
+import java.util.BitSet
 import javax.inject.Inject
 
 class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() {
@@ -24,19 +27,20 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
 
     private var items = mutableListOf<GradeDetailsItem>()
 
-    private var expandedPosition = NO_POSITION
+    private val expandedPositions = BitSet(items.size)
 
-    private var isExpandable = false
+    private var expandMode = GradeExpandMode.ONE
 
     var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> }
 
     var colorTheme = ""
 
-    fun setDataItems(data: List<GradeDetailsItem>, isExpanded: Boolean = isExpandable) {
+    fun setDataItems(data: List<GradeDetailsItem>, expandMode: GradeExpandMode = this.expandMode) {
         headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
-        items = if (isExpanded) headers else data.toMutableList()
-        isExpandable = isExpanded
-        expandedPosition = NO_POSITION
+        items =
+            (if (expandMode != GradeExpandMode.ALWAYS_EXPANDED) headers else data).toMutableList()
+        this.expandMode = expandMode
+        expandedPositions.clear()
     }
 
     fun updateDetailsItem(position: Int, grade: Grade) {
@@ -48,7 +52,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
         val candidates = headers.filter { (it.value as GradeDetailsHeader).subject == subject }
 
         if (candidates.size > 1) {
-            Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPosition. Items: $candidates")
+            Timber.e("Header with subject $subject found ${candidates.size} times! Expanded: $expandedPositions. Items: $candidates")
         }
 
         return candidates.first()
@@ -64,9 +68,9 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
     }
 
     fun collapseAll() {
-        if (expandedPosition != -1) {
-            refreshList(headers)
-            expandedPosition = NO_POSITION
+        if (!expandedPositions.isEmpty) {
+            refreshList(headers.toMutableList())
+            expandedPositions.clear()
         }
     }
 
@@ -86,8 +90,12 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
         val inflater = LayoutInflater.from(parent.context)
 
         return when (viewType) {
-            ViewType.HEADER.id -> HeaderViewHolder(HeaderGradeDetailsBinding.inflate(inflater, parent, false))
-            ViewType.ITEM.id -> ItemViewHolder(ItemGradeDetailsBinding.inflate(inflater, parent, false))
+            ViewType.HEADER.id -> HeaderViewHolder(
+                HeaderGradeDetailsBinding.inflate(inflater, parent, false)
+            )
+            ViewType.ITEM.id -> ItemViewHolder(
+                ItemGradeDetailsBinding.inflate(inflater, parent, false)
+            )
             else -> throw IllegalStateException()
         }
     }
@@ -106,46 +114,91 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
         }
     }
 
-    private fun bindHeaderViewHolder(holder: HeaderViewHolder, header: GradeDetailsHeader, position: Int) {
-        val headerPosition = headers.indexOf(items[position])
-        val adapterPosition = holder.bindingAdapterPosition
+    private fun bindHeaderViewHolder(
+        holder: HeaderViewHolder,
+        header: GradeDetailsHeader,
+        position: Int
+    ) {
+        val context = holder.binding.root.context
+        val item = items[position]
+        val headerPosition = headers.indexOf(item)
 
         with(holder.binding) {
-            gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE
+            gradeHeaderDivider.isVisible = holder.bindingAdapterPosition != 0
             with(gradeHeaderSubject) {
                 text = header.subject
-                maxLines = if (headerPosition == expandedPosition) 2 else 1
+                maxLines = if (expandedPositions[headerPosition]) 2 else 1
             }
             gradeHeaderAverage.text = formatAverage(header.average, root.context.resources)
-            gradeHeaderPointsSum.text = root.context.getString(R.string.grade_points_sum, header.pointsSum)
-            gradeHeaderPointsSum.visibility = if (!header.pointsSum.isNullOrEmpty()) View.VISIBLE else View.GONE
-            gradeHeaderNumber.text = root.context.resources.getQuantityString(R.plurals.grade_number_item, header.grades.size, header.grades.size)
-            gradeHeaderNote.visibility = if (header.newGrades > 0) View.VISIBLE else View.GONE
-            if (header.newGrades > 0) gradeHeaderNote.text = header.newGrades.toString(10)
+            gradeHeaderPointsSum.text =
+                context.getString(R.string.grade_points_sum, header.pointsSum)
+            gradeHeaderPointsSum.isVisible = !header.pointsSum.isNullOrEmpty()
+            gradeHeaderNumber.text = context.resources.getQuantityString(
+                R.plurals.grade_number_item,
+                header.grades.size,
+                header.grades.size
+            )
+            gradeHeaderNote.isVisible = header.newGrades > 0
 
-            gradeHeaderContainer.isEnabled = isExpandable
+            if (header.newGrades > 0) {
+                gradeHeaderNote.text = header.newGrades.toString()
+            }
+
+            gradeHeaderContainer.isEnabled = expandMode != GradeExpandMode.ALWAYS_EXPANDED
             gradeHeaderContainer.setOnClickListener {
-                expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition
-
-                if (expandedPosition != NO_POSITION) {
-                    refreshList(headers.toMutableList().apply {
-                        addAll(headerPosition + 1, header.grades)
-                    })
-                    scrollToHeaderWithSubItems(headerPosition, header.grades.size)
-                } else {
-                    refreshList(headers)
-                }
+                expandGradeHeader(headerPosition, header, holder)
             }
         }
     }
 
-    private fun formatAverage(average: Double?, resources: Resources): String {
-        return if (average == null || average == .0) resources.getString(R.string.grade_no_average)
-        else resources.getString(R.string.grade_average, average)
+    private fun expandGradeHeader(
+        headerPosition: Int,
+        header: GradeDetailsHeader,
+        holder: HeaderViewHolder
+    ) {
+        if (expandMode == GradeExpandMode.ONE) {
+            val isHeaderExpanded = expandedPositions[headerPosition]
+
+            expandedPositions.clear()
+
+            if (!isHeaderExpanded) {
+                val updatedItemList = headers.toMutableList()
+                    .apply { addAll(headerPosition + 1, header.grades) }
+
+                expandedPositions.set(headerPosition)
+                refreshList(updatedItemList)
+                scrollToHeaderWithSubItems(headerPosition, header.grades.size)
+            } else {
+                refreshList(headers.toMutableList())
+            }
+        } else if (expandMode == GradeExpandMode.UNLIMITED) {
+            val headerAdapterPosition = holder.bindingAdapterPosition
+            val isHeaderExpanded = expandedPositions[headerPosition]
+
+            expandedPositions.flip(headerPosition)
+
+            if (!isHeaderExpanded) {
+                val updatedList = items.toMutableList()
+                    .apply { addAll(headerAdapterPosition + 1, header.grades) }
+
+                refreshList(updatedList)
+                scrollToHeaderWithSubItems(headerAdapterPosition, header.grades.size)
+            } else {
+                val startPosition = headerAdapterPosition + 1
+                val updatedList = items.toMutableList()
+                    .apply {
+                        subList(startPosition, startPosition + header.grades.size).clear()
+                    }
+
+                refreshList(updatedList)
+            }
+        }
     }
 
     @SuppressLint("SetTextI18n")
     private fun bindItemViewHolder(holder: ItemViewHolder, grade: Grade) {
+        val context = holder.binding.root.context
+
         with(holder.binding) {
             gradeItemValue.run {
                 text = grade.entry
@@ -154,26 +207,37 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
             gradeItemDescription.text = when {
                 grade.description.isNotBlank() -> grade.description
                 grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol
-                else -> root.context.getString(R.string.all_no_description)
+                else -> context.getString(R.string.all_no_description)
             }
             gradeItemDate.text = grade.date.toFormattedString()
-            gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}"
+            gradeItemWeight.text = "${context.getString(R.string.grade_weight)}: ${grade.weight}"
             gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE
 
             root.setOnClickListener {
-                holder.bindingAdapterPosition.let { if (it != NO_POSITION) onClickListener(grade, it) }
+                holder.bindingAdapterPosition.let {
+                    if (it != NO_POSITION) onClickListener(grade, it)
+                }
             }
         }
     }
 
+    private fun formatAverage(average: Double?, resources: Resources) =
+        if (average == null || average == .0) {
+            resources.getString(R.string.grade_no_average)
+        } else {
+            resources.getString(R.string.grade_average, average)
+        }
+
     private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) :
         RecyclerView.ViewHolder(binding.root)
 
     private class ItemViewHolder(val binding: ItemGradeDetailsBinding) :
         RecyclerView.ViewHolder(binding.root)
 
-    class GradeDetailsDiffUtil(private val old: List<GradeDetailsItem>, private val new: List<GradeDetailsItem>) :
-        DiffUtil.Callback() {
+    private class GradeDetailsDiffUtil(
+        private val old: List<GradeDetailsItem>,
+        private val new: List<GradeDetailsItem>
+    ) : DiffUtil.Callback() {
 
         override fun getOldListSize() = old.size
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
index 9d4da767..c93600d4 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt
@@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
 import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding
 import io.github.wulkanowy.ui.base.BaseFragment
 import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -79,10 +80,10 @@ class GradeDetailsFragment :
         else false
     }
 
-    override fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String) {
+    override fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: String) {
         with(gradeDetailsAdapter) {
             colorTheme = gradeColorTheme
-            setDataItems(data, isGradeExpandable)
+            setDataItems(data, expandMode)
             notifyDataSetChanged()
         }
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
index 7544d2aa..54d4f461 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.ui.base.BasePresenter
 import io.github.wulkanowy.ui.base.ErrorHandler
 import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
 import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
 import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
 import io.github.wulkanowy.ui.modules.grade.GradeSubject
@@ -16,6 +17,7 @@ import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResource
 import io.github.wulkanowy.utils.flowWithResourceIn
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
@@ -46,8 +48,8 @@ class GradeDetailsPresenter @Inject constructor(
     fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
         currentSemesterId = semesterId
 
-        loadData(semesterId, forceRefresh)
         if (!forceRefresh) view?.showErrorView(false)
+        loadData(semesterId, forceRefresh)
     }
 
     fun onGradeItemSelected(grade: Grade, position: Int) {
@@ -113,7 +115,7 @@ class GradeDetailsPresenter @Inject constructor(
     fun onParentViewReselected() {
         view?.run {
             if (!isViewEmpty) {
-                if (preferencesRepository.isGradeExpandable) collapseAllItems()
+                if (preferencesRepository.gradeExpandMode != GradeExpandMode.ALWAYS_EXPANDED) collapseAllItems()
                 scrollToStart()
             }
         }
@@ -157,7 +159,7 @@ class GradeDetailsPresenter @Inject constructor(
                             showContent(true)
                             updateData(
                                 data = items,
-                                isGradeExpandable = preferencesRepository.isGradeExpandable,
+                                expandMode = preferencesRepository.gradeExpandMode,
                                 gradeColorTheme = preferencesRepository.gradeColorTheme
                             )
                             notifyParentDataLoaded(semesterId)
@@ -175,7 +177,7 @@ class GradeDetailsPresenter @Inject constructor(
                         showContent(items.isNotEmpty())
                         updateData(
                             data = items,
-                            isGradeExpandable = preferencesRepository.isGradeExpandable,
+                            expandMode = preferencesRepository.gradeExpandMode,
                             gradeColorTheme = preferencesRepository.gradeColorTheme
                         )
                     }
@@ -197,6 +199,9 @@ class GradeDetailsPresenter @Inject constructor(
                 enableSwipe(true)
                 notifyParentDataLoaded(semesterId)
             }
+        }.catch {
+            errorHandler.dispatch(it)
+            view?.notifyParentDataLoaded(semesterId)
         }.launch()
     }
 
@@ -213,6 +218,7 @@ class GradeDetailsPresenter @Inject constructor(
                 setErrorDetails(message)
                 showErrorView(true)
                 showEmpty(false)
+                showProgress(false)
             } else showError(message, error)
         }
     }
@@ -235,14 +241,24 @@ class GradeDetailsPresenter @Inject constructor(
                     .sortedByDescending { it.date }
                     .map { GradeDetailsItem(it, ViewType.ITEM) }
 
-                listOf(GradeDetailsItem(GradeDetailsHeader(
-                    subject = subject,
-                    average = average,
-                    pointsSum = points,
-                    grades = subItems
-                ).apply {
-                    newGrades = grades.filter { grade -> !grade.isRead }.size
-                }, ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
+                val gradeDetailsItems = listOf(
+                    GradeDetailsItem(
+                        GradeDetailsHeader(
+                            subject = subject,
+                            average = average,
+                            pointsSum = points,
+                            grades = subItems
+                        ).apply {
+                            newGrades = grades.filter { grade -> !grade.isRead }.size
+                        }, ViewType.HEADER
+                    )
+                )
+
+                if (preferencesRepository.gradeExpandMode == GradeExpandMode.ALWAYS_EXPANDED) {
+                    gradeDetailsItems + subItems
+                } else {
+                    gradeDetailsItems
+                }
             }.flatten()
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt
index e71fcc3c..55633229 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsView.kt
@@ -1,6 +1,7 @@
 package io.github.wulkanowy.ui.modules.grade.details
 
 import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
 import io.github.wulkanowy.ui.base.BaseView
 
 interface GradeDetailsView : BaseView {
@@ -9,7 +10,7 @@ interface GradeDetailsView : BaseView {
 
     fun initView()
 
-    fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String)
+    fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: String)
 
     fun updateItem(item: Grade, position: Int)
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
index 0adac300..dbc4c10c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt
@@ -68,7 +68,7 @@ class GradeStatisticsFragment :
         }
 
         with(binding) {
-            gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
+            gradeStatisticsSubjectsContainer.elevation = requireContext().dpToPx(1f)
 
             gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
             gradeStatisticsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
index 1d9434dc..d4eaade2 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt
@@ -10,6 +10,7 @@ import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Homework
 import io.github.wulkanowy.databinding.FragmentHomeworkBinding
 import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.homework.add.HomeworkAddDialog
 import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog
 import io.github.wulkanowy.ui.modules.main.MainActivity
 import io.github.wulkanowy.ui.modules.main.MainView
@@ -64,7 +65,9 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
             homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() }
             homeworkNextButton.setOnClickListener { presenter.onNextDay() }
 
-            homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() }
+
+            homeworkNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
@@ -122,10 +125,14 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
         binding.homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE
     }
 
-    override fun showTimetableDialog(homework: Homework) {
+    override fun showHomeworkDialog(homework: Homework) {
         (activity as? MainActivity)?.showDialogFragment(HomeworkDetailsDialog.newInstance(homework))
     }
 
+    override fun showAddHomeworkDialog() {
+        (activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog())
+    }
+
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt
index 11c54dc2..d7d5d7cb 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt
@@ -78,7 +78,11 @@ class HomeworkPresenter @Inject constructor(
 
     fun onHomeworkItemSelected(homework: Homework) {
         Timber.i("Select homework item ${homework.id}")
-        view?.showTimetableDialog(homework)
+        view?.showHomeworkDialog(homework)
+    }
+
+    fun onHomeworkAddButtonClicked() {
+        view?.showAddHomeworkDialog()
     }
 
     private fun setBaseDateOnHolidays() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt
index a1d6a04a..7c05ab86 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt
@@ -33,5 +33,7 @@ interface HomeworkView : BaseView {
 
     fun showNextButton(show: Boolean)
 
-    fun showTimetableDialog(homework: Homework)
+    fun showHomeworkDialog(homework: Homework)
+
+    fun showAddHomeworkDialog()
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt
new file mode 100644
index 00000000..12168f14
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt
@@ -0,0 +1,124 @@
+package io.github.wulkanowy.ui.modules.homework.add
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.widget.doOnTextChanged
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.MaterialDatePicker
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.databinding.DialogHomeworkAddBinding
+import io.github.wulkanowy.ui.base.BaseDialogFragment
+import io.github.wulkanowy.utils.toFormattedString
+import io.github.wulkanowy.utils.toLocalDateTime
+import io.github.wulkanowy.utils.toTimestamp
+import java.time.LocalDate
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class HomeworkAddDialog : BaseDialogFragment<DialogHomeworkAddBinding>(), HomeworkAddView {
+
+    @Inject
+    lateinit var presenter: HomeworkAddPresenter
+
+    private var date: LocalDate? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setStyle(STYLE_NO_TITLE, 0)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ) = DialogHomeworkAddBinding.inflate(inflater).apply { binding = this }.root
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        presenter.onAttachView(this)
+    }
+
+    override fun initView() {
+        with(binding) {
+            homeworkDialogSubjectEdit.doOnTextChanged { _, _, _, _ ->
+                homeworkDialogSubject.error = null
+                homeworkDialogSubject.isErrorEnabled = false
+            }
+            homeworkDialogDateEdit.doOnTextChanged { _, _, _, _ ->
+                homeworkDialogDate.error = null
+                homeworkDialogDate.isErrorEnabled = false
+            }
+            homeworkDialogContentEdit.doOnTextChanged { _, _, _, _ ->
+                homeworkDialogContent.error = null
+                homeworkDialogContent.isErrorEnabled = false
+            }
+            homeworkDialogClose.setOnClickListener { dismiss() }
+            homeworkDialogDateEdit.setOnClickListener { presenter.showDatePicker(date) }
+            homeworkDialogAdd.setOnClickListener {
+                presenter.onAddHomeworkClicked(
+                    subject = homeworkDialogSubjectEdit.text?.toString(),
+                    teacher = homeworkDialogTeacherEdit.text?.toString(),
+                    date = homeworkDialogDateEdit.text?.toString(),
+                    content = homeworkDialogContentEdit.text?.toString()
+                )
+            }
+        }
+    }
+
+    override fun showSuccessMessage() {
+        showMessage(getString(R.string.homework_add_success))
+    }
+
+    override fun setErrorSubjectRequired() {
+        with(binding.homeworkDialogSubject) {
+            isErrorEnabled = true
+            error = getString(R.string.error_field_required)
+        }
+    }
+
+    override fun setErrorDateRequired() {
+        with(binding.homeworkDialogDate) {
+            isErrorEnabled = true
+            error = getString(R.string.error_field_required)
+        }
+    }
+
+    override fun setErrorContentRequired() {
+        with(binding.homeworkDialogContent) {
+            isErrorEnabled = true
+            error = getString(R.string.error_field_required)
+        }
+    }
+
+    override fun closeDialog() {
+        dismiss()
+    }
+
+    override fun showDatePickerDialog(currentDate: LocalDate) {
+        val constraintsBuilder = CalendarConstraints.Builder().apply {
+            setStart(LocalDate.now().toEpochDay())
+        }
+        val datePicker =
+            MaterialDatePicker.Builder.datePicker()
+                .setCalendarConstraints(constraintsBuilder.build())
+                .setSelection(currentDate.toTimestamp())
+                .build()
+
+        datePicker.addOnPositiveButtonClickListener {
+            date = it.toLocalDateTime().toLocalDate()
+            binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString())
+        }
+
+        if (!parentFragmentManager.isStateSaved) {
+            datePicker.show(this.parentFragmentManager, null)
+        }
+    }
+
+    override fun onDestroyView() {
+        presenter.onDetachView()
+        super.onDestroyView()
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt
new file mode 100644
index 00000000..3639c2fe
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt
@@ -0,0 +1,92 @@
+package io.github.wulkanowy.ui.modules.homework.add
+
+import io.github.wulkanowy.data.Status
+import io.github.wulkanowy.data.db.entities.Homework
+import io.github.wulkanowy.data.repositories.HomeworkRepository
+import io.github.wulkanowy.data.repositories.SemesterRepository
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.utils.flowWithResource
+import io.github.wulkanowy.utils.toLocalDate
+import kotlinx.coroutines.flow.onEach
+import timber.log.Timber
+import java.time.LocalDate
+import javax.inject.Inject
+
+class HomeworkAddPresenter @Inject constructor(
+    errorHandler: ErrorHandler,
+    studentRepository: StudentRepository,
+    private val homeworkRepository: HomeworkRepository,
+    private val semesterRepository: SemesterRepository
+) : BasePresenter<HomeworkAddView>(errorHandler, studentRepository) {
+
+    override fun onAttachView(view: HomeworkAddView) {
+        super.onAttachView(view)
+        view.initView()
+        Timber.i("Homework details view was initialized")
+    }
+
+    fun showDatePicker(date: LocalDate?) {
+        view?.showDatePickerDialog(date ?: LocalDate.now())
+    }
+
+    fun onAddHomeworkClicked(subject: String?, teacher: String?, date: String?, content: String?) {
+        var isError = false
+
+        if (subject.isNullOrBlank()) {
+            view?.setErrorSubjectRequired()
+            isError = true
+        }
+
+        if (date.isNullOrBlank()) {
+            view?.setErrorDateRequired()
+            isError = true
+        }
+
+        if (content.isNullOrBlank()) {
+            view?.setErrorContentRequired()
+            isError = true
+        }
+
+        if (!isError) {
+            saveHomework(subject!!, teacher.orEmpty(), date!!.toLocalDate(), content!!)
+        }
+    }
+
+    private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) {
+        flowWithResource {
+            val student = studentRepository.getCurrentStudent()
+            val semester = semesterRepository.getCurrentSemester(student)
+            val entryDate = LocalDate.now()
+            homeworkRepository.saveHomework(
+                Homework(
+                    semesterId = semester.semesterId,
+                    studentId = student.studentId,
+                    date = date,
+                    entryDate = entryDate,
+                    subject = subject,
+                    content = content,
+                    teacher = teacher,
+                    teacherSymbol = "",
+                    attachments = emptyList(),
+                ).apply { isAddedByUser = true }
+            )
+        }.onEach {
+            when (it.status) {
+                Status.LOADING -> Timber.i("Homework insert start")
+                Status.SUCCESS -> {
+                    Timber.i("Homework insert: Success")
+                    view?.run {
+                        showSuccessMessage()
+                        closeDialog()
+                    }
+                }
+                Status.ERROR -> {
+                    Timber.i("Homework insert result: An exception occurred")
+                    errorHandler.dispatch(it.error!!)
+                }
+            }
+        }.launch("add_homework")
+    }
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt
new file mode 100644
index 00000000..3bb304d9
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddView.kt
@@ -0,0 +1,21 @@
+package io.github.wulkanowy.ui.modules.homework.add
+
+import io.github.wulkanowy.ui.base.BaseView
+import java.time.LocalDate
+
+interface HomeworkAddView : BaseView {
+
+    fun initView()
+
+    fun showSuccessMessage()
+
+    fun setErrorSubjectRequired()
+
+    fun setErrorDateRequired()
+
+    fun setErrorContentRequired()
+
+    fun closeDialog()
+
+    fun showDatePickerDialog(currentDate: LocalDate)
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt
index cd9a7e85..e03707a5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt
@@ -5,10 +5,12 @@ import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
+import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Homework
 import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentBinding
 import io.github.wulkanowy.databinding.ItemHomeworkDialogAttachmentsHeaderBinding
 import io.github.wulkanowy.databinding.ItemHomeworkDialogDetailsBinding
+import io.github.wulkanowy.utils.ifNullOrBlank
 import io.github.wulkanowy.utils.toFormattedString
 import javax.inject.Inject
 
@@ -37,6 +39,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
 
     var onFullScreenExitClickListener = {}
 
+    var onDeleteClickListener: (homework: Homework) -> Unit = {}
+
     override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0
 
     override fun getItemViewType(position: Int) = when (position) {
@@ -49,9 +53,15 @@ class HomeworkDetailsAdapter @Inject constructor() :
         val inflater = LayoutInflater.from(parent.context)
 
         return when (viewType) {
-            ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder(ItemHomeworkDialogAttachmentsHeaderBinding.inflate(inflater, parent, false))
-            ViewType.ATTACHMENT.id -> AttachmentViewHolder(ItemHomeworkDialogAttachmentBinding.inflate(inflater, parent, false))
-            else -> DetailsViewHolder(ItemHomeworkDialogDetailsBinding.inflate(inflater, parent, false))
+            ViewType.ATTACHMENTS_HEADER.id -> AttachmentsHeaderViewHolder(
+                ItemHomeworkDialogAttachmentsHeaderBinding.inflate(inflater, parent, false)
+            )
+            ViewType.ATTACHMENT.id -> AttachmentViewHolder(
+                ItemHomeworkDialogAttachmentBinding.inflate(inflater, parent, false)
+            )
+            else -> DetailsViewHolder(
+                ItemHomeworkDialogDetailsBinding.inflate(inflater, parent, false)
+            )
         }
     }
 
@@ -63,12 +73,15 @@ class HomeworkDetailsAdapter @Inject constructor() :
     }
 
     private fun bindDetailsViewHolder(holder: DetailsViewHolder) {
+        val noDataString = holder.binding.root.context.getString(R.string.all_no_data)
+
         with(holder.binding) {
             homeworkDialogDate.text = homework?.date?.toFormattedString()
             homeworkDialogEntryDate.text = homework?.entryDate?.toFormattedString()
-            homeworkDialogSubject.text = homework?.subject
-            homeworkDialogTeacher.text = homework?.teacher
-            homeworkDialogContent.text = homework?.content
+            homeworkDialogSubject.text = homework?.subject.ifNullOrBlank { noDataString }
+            homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString }
+            homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString }
+            homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE
             homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE
             homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE
             homeworkDialogFullScreen.setOnClickListener {
@@ -81,6 +94,9 @@ class HomeworkDetailsAdapter @Inject constructor() :
                 homeworkDialogFullScreenExit.visibility = GONE
                 onFullScreenExitClickListener()
             }
+            homeworkDialogDelete.setOnClickListener {
+                onDeleteClickListener(homework!!)
+            }
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
index 93045a48..f9d46351 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt
@@ -25,6 +25,9 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
     @Inject
     lateinit var detailsAdapter: HomeworkDetailsAdapter
 
+    override val homeworkDeleteSuccess: String
+        get() = getString(R.string.homework_delete_success)
+
     private lateinit var homework: Homework
 
     companion object {
@@ -82,12 +85,17 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
                     dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
                     presenter.isHomeworkFullscreen = false
                 }
+                onDeleteClickListener = { homework -> presenter.deleteHomework(homework) }
                 isHomeworkFullscreen = presenter.isHomeworkFullscreen
                 homework = this@HomeworkDetailsDialog.homework
             }
         }
     }
 
+    override fun closeDialog() {
+        dismiss()
+    }
+
     override fun updateMarkAsDoneLabel(isDone: Boolean) {
         binding.homeworkDialogRead.text =
             view?.context?.getString(if (isDone) R.string.homework_mark_as_undone else R.string.homework_mark_as_done)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt
index ca6fc71e..ea9b47a0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt
@@ -33,6 +33,25 @@ class HomeworkDetailsPresenter @Inject constructor(
         Timber.i("Homework details view was initialized")
     }
 
+    fun deleteHomework(homework: Homework) {
+        flowWithResource { homeworkRepository.deleteHomework(homework) }.onEach {
+            when (it.status) {
+                Status.LOADING -> Timber.i("Homework delete start")
+                Status.SUCCESS -> {
+                    Timber.i("Homework delete: Success")
+                    view?.run {
+                        showMessage(homeworkDeleteSuccess)
+                        closeDialog()
+                    }
+                }
+                Status.ERROR -> {
+                    Timber.i("Homework delete result: An exception occurred")
+                    errorHandler.dispatch(it.error!!)
+                }
+            }
+        }.launch("delete")
+    }
+
     fun toggleDone(homework: Homework) {
         flowWithResource { homeworkRepository.toggleDone(homework) }.onEach {
             when (it.status) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt
index 697f2233..4a47de43 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsView.kt
@@ -4,7 +4,11 @@ import io.github.wulkanowy.ui.base.BaseView
 
 interface HomeworkDetailsView : BaseView {
 
+    val homeworkDeleteSuccess: String
+
     fun initView()
 
+    fun closeDialog()
+
     fun updateMarkAsDoneLabel(isDone: Boolean)
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
index 10f6c073..70b54c49 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt
@@ -24,18 +24,24 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
     @Inject
     override lateinit var presenter: LoginPresenter
 
-    private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
+    private val pagerAdapter by lazy {
+        BaseFragmentPagerAdapter(
+            fragmentManager = supportFragmentManager,
+            pagesCount = 5,
+            lifecycle = lifecycle,
+        )
+    }
 
     @Inject
     lateinit var updateHelper: UpdateHelper
 
+    override val currentViewIndex get() = binding.loginViewpager.currentItem
+
     companion object {
 
         fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
     }
 
-    override val currentViewIndex get() = binding.loginViewpager.currentItem
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root)
@@ -65,24 +71,26 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
             setDisplayShowTitleEnabled(false)
         }
 
-        with(loginAdapter) {
-            containerId = binding.loginViewpager.id
-            addFragments(
-                listOf(
-                    LoginFormFragment.newInstance(),
-                    LoginSymbolFragment.newInstance(),
-                    LoginStudentSelectFragment.newInstance(),
-                    LoginAdvancedFragment.newInstance(),
-                    LoginRecoverFragment.newInstance()
-                )
-            )
-        }
-
         with(binding.loginViewpager) {
             offscreenPageLimit = 2
-            adapter = loginAdapter
+            adapter = pagerAdapter
+            isUserInputEnabled = false
             setOnSelectPageListener(presenter::onViewSelected)
         }
+
+        with(pagerAdapter) {
+            containerId = binding.loginViewpager.id
+            itemFactory = {
+                when (it) {
+                    0 -> LoginFormFragment.newInstance()
+                    1 -> LoginSymbolFragment.newInstance()
+                    2 -> LoginStudentSelectFragment.newInstance()
+                    3 -> LoginAdvancedFragment.newInstance()
+                    4 -> LoginRecoverFragment.newInstance()
+                    else -> throw IllegalStateException()
+                }
+            }
+        }
     }
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -103,12 +111,12 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
     }
 
     override fun notifyInitSymbolFragment(loginData: Triple<String, String, String>) {
-        (loginAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
+        (pagerAdapter.getFragmentInstance(1) as? LoginSymbolFragment)
             ?.onParentInitSymbolFragment(loginData)
     }
 
     override fun notifyInitStudentSelectFragment(studentsWithSemesters: List<StudentWithSemesters>) {
-        (loginAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)
+        (pagerAdapter.getFragmentInstance(2) as? LoginStudentSelectFragment)
             ?.onParentInitStudentSelectFragment(studentsWithSemesters)
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
index 2f76cd51..ea7215ce 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt
@@ -1,7 +1,8 @@
 package io.github.wulkanowy.ui.modules.login
 
-import android.content.res.Resources
+import android.content.Context
 import android.database.sqlite.SQLiteConstraintException
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.R
 import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
 import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException
@@ -11,7 +12,8 @@ import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
 import io.github.wulkanowy.ui.base.ErrorHandler
 import javax.inject.Inject
 
-class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
+class LoginErrorHandler @Inject constructor(@ApplicationContext context: Context) :
+    ErrorHandler(context) {
 
     var onBadCredentials: (String?) -> Unit = {}
 
@@ -24,6 +26,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
     var onStudentDuplicate: (String) -> Unit = {}
 
     override fun proceed(error: Throwable) {
+        val resources = context.resources
         when (error) {
             is BadCredentialsException -> onBadCredentials(error.message)
             is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt
index 0672d75f..bc29cd14 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedFragment.kt
@@ -170,7 +170,7 @@ class LoginAdvancedFragment :
     override fun setErrorUsernameRequired() {
         with(binding.loginFormUsernameLayout) {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -191,7 +191,7 @@ class LoginAdvancedFragment :
     override fun setErrorPassRequired(focus: Boolean) {
         with(binding.loginFormPassLayout) {
             if (focus) requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -205,14 +205,14 @@ class LoginAdvancedFragment :
     override fun setErrorPassIncorrect(message: String?) {
         with(binding.loginFormPassLayout) {
             requestFocus()
-            error = message ?: getString(R.string.login_incorrect_password)
+            error = message ?: getString(R.string.login_incorrect_password_default)
         }
     }
 
     override fun setErrorPinRequired() {
         with(binding.loginFormPinLayout) {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -226,7 +226,7 @@ class LoginAdvancedFragment :
     override fun setErrorSymbolRequired() {
         with(binding.loginFormSymbolLayout) {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -240,7 +240,7 @@ class LoginAdvancedFragment :
     override fun setErrorTokenRequired() {
         with(binding.loginFormTokenLayout) {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
index 6e0294a4..c741da42 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt
@@ -123,7 +123,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
 
     override fun setErrorUsernameRequired() {
         with(binding.loginFormUsernameLayout) {
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -141,7 +141,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
 
     override fun setErrorPassRequired(focus: Boolean) {
         with(binding.loginFormPassLayout) {
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -152,12 +152,11 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
     }
 
     override fun setErrorPassIncorrect(message: String?) {
-        val error = message ?: getString(R.string.login_incorrect_password_default)
-
         with(binding) {
             loginFormUsernameLayout.error = " "
             loginFormPassLayout.error = " "
-            loginFormErrorBox.text = getString(R.string.login_incorrect_password, error)
+            loginFormHostLayout.error = " "
+            loginFormErrorBox.text = message ?: getString(R.string.login_incorrect_password_default)
             loginFormErrorBox.isVisible = true
         }
     }
@@ -178,6 +177,11 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
         binding.loginFormErrorBox.isVisible = false
     }
 
+    override fun clearHostError() {
+        binding.loginFormHostLayout.error = null
+        binding.loginFormErrorBox.isVisible = false
+    }
+
     override fun showSoftKeyboard() {
         activity?.showSoftInput()
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
index bd876b84..21cdf2a0 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt
@@ -30,7 +30,7 @@ class LoginFormPresenter @Inject constructor(
             showVersion()
 
             loginErrorHandler.onBadCredentials = {
-                setErrorPassIncorrect(it)
+                setErrorPassIncorrect(it.takeIf { !it.isNullOrBlank() })
                 showSoftKeyboard()
                 Timber.i("Entered wrong username or password")
             }
@@ -49,6 +49,7 @@ class LoginFormPresenter @Inject constructor(
         view?.apply {
             clearPassError()
             clearUsernameError()
+            clearHostError()
             if (formHostValue.contains("fakelog")) {
                 setCredentials("jan@fakelog.cf", "jan123")
             }
@@ -75,7 +76,10 @@ class LoginFormPresenter @Inject constructor(
             val usernameHost = username.substringAfter("@")
 
             hosts[usernameHost]?.let {
-                view?.setHost(it)
+                view?.run {
+                    setHost(it)
+                    clearHostError()
+                }
             }
         }
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
index efdaa082..30057355 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt
@@ -45,6 +45,8 @@ interface LoginFormView : BaseView {
 
     fun clearPassError()
 
+    fun clearHostError()
+
     fun showSoftKeyboard()
 
     fun hideSoftKeyboard()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
index 2e2f9f5c..a91dfb61 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt
@@ -99,7 +99,7 @@ class LoginRecoverFragment :
     override fun setErrorNameRequired() {
         with(bindingLocal.loginRecoverNameLayout) {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt
index 8619369d..ac4c0313 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt
@@ -1,13 +1,15 @@
 package io.github.wulkanowy.ui.modules.login.recover
 
-import android.content.res.Resources
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.sdk.scrapper.exception.InvalidCaptchaException
 import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException
 import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException
 import io.github.wulkanowy.ui.base.ErrorHandler
 import javax.inject.Inject
 
-class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
+class RecoverErrorHandler @Inject constructor(@ApplicationContext context: Context) :
+    ErrorHandler(context) {
 
     var onInvalidUsername: (String) -> Unit = {}
 
@@ -15,7 +17,8 @@ class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandl
 
     override fun proceed(error: Throwable) {
         when (error) {
-            is InvalidEmailException, is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
+            is InvalidEmailException,
+            is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
             is InvalidCaptchaException -> onInvalidCaptcha(error.localizedMessage.orEmpty(), error)
             else -> super.proceed(error)
         }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
index e71fc0f6..87cb505c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt
@@ -66,7 +66,8 @@ class LoginStudentSelectFragment :
     }
 
     override fun openMainView() {
-        activity?.let { startActivity(MainActivity.getStartIntent(context = it, clear = true)) }
+        startActivity(MainActivity.getStartIntent(requireContext()))
+        requireActivity().finish()
     }
 
     override fun showProgress(show: Boolean) {
@@ -108,7 +109,8 @@ class LoginStudentSelectFragment :
             chooserTitle = requireContext().getString(R.string.login_email_intent_title),
             email = "wulkanowyinc@gmail.com",
             subject = requireContext().getString(R.string.login_email_subject),
-            body = requireContext().getString(R.string.login_email_text, appInfo.systemModel,
+            body = requireContext().getString(
+                R.string.login_email_text, appInfo.systemModel,
                 appInfo.systemVersion.toString(),
                 appInfo.versionName,
                 "Select users to log in",
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
index e2c37db6..a8086935 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt
@@ -7,6 +7,7 @@ import android.view.View.VISIBLE
 import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
 import android.view.inputmethod.EditorInfo.IME_NULL
 import android.widget.ArrayAdapter
+import androidx.core.text.parseAsHtml
 import androidx.core.widget.doOnTextChanged
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
@@ -58,7 +59,13 @@ class LoginSymbolFragment :
                 setOnEditorActionListener { _, id, _ ->
                     if (id == IME_ACTION_DONE || id == IME_NULL) loginSymbolSignIn.callOnClick() else false
                 }
-                setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, resources.getStringArray(R.array.symbols_values)))
+                setAdapter(
+                    ArrayAdapter(
+                        context,
+                        android.R.layout.simple_list_item_1,
+                        resources.getStringArray(R.array.symbols_values)
+                    )
+                )
             }
         }
     }
@@ -67,6 +74,10 @@ class LoginSymbolFragment :
         presenter.onParentInitSymbolView(loginData)
     }
 
+    override fun setLoginToHeading(login: String) {
+        binding.loginSymbolHeader.text = getString(R.string.login_header_symbol, login).parseAsHtml()
+    }
+
     override fun setErrorSymbolIncorrect() {
         binding.loginSymbolNameLayout.apply {
             requestFocus()
@@ -77,7 +88,7 @@ class LoginSymbolFragment :
     override fun setErrorSymbolRequire() {
         binding.loginSymbolNameLayout.apply {
             requestFocus()
-            error = getString(R.string.login_field_required)
+            error = getString(R.string.error_field_required)
         }
     }
 
@@ -127,7 +138,10 @@ class LoginSymbolFragment :
     }
 
     override fun openFaqPage() {
-        context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/co-to-jest-symbol", ::showMessage)
+        context?.openInternetBrowser(
+            "https://wulkanowy.github.io/czesto-zadawane-pytania/co-to-jest-symbol",
+            ::showMessage
+        )
     }
 
     override fun openEmail(host: String, lastError: String) {
@@ -135,7 +149,8 @@ class LoginSymbolFragment :
             chooserTitle = requireContext().getString(R.string.login_email_intent_title),
             email = "wulkanowyinc@gmail.com",
             subject = requireContext().getString(R.string.login_email_subject),
-            body = requireContext().getString(R.string.login_email_text,
+            body = requireContext().getString(
+                R.string.login_email_text,
                 "${appInfo.systemManufacturer} ${appInfo.systemModel}",
                 appInfo.systemVersion.toString(),
                 appInfo.versionName,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
index 4593d880..7c84e1ec 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt
@@ -32,6 +32,16 @@ class LoginSymbolPresenter @Inject constructor(
         }
         if (savedLoginData is Triple<*, *, *>) {
             loginData = savedLoginData as Triple<String, String, String>
+            view.setLoginToHeading(requireNotNull(loginData?.first))
+        }
+    }
+
+    fun onParentInitSymbolView(loginData: Triple<String, String, String>) {
+        this.loginData = loginData
+        view?.apply {
+            setLoginToHeading(loginData.first)
+            clearAndFocusSymbol()
+            showSoftKeyboard()
         }
     }
 
@@ -47,7 +57,14 @@ class LoginSymbolPresenter @Inject constructor(
             return
         }
 
-        flowWithResource { studentRepository.getStudentsScrapper(loginData!!.first, loginData!!.second, loginData!!.third, symbol) }.onEach {
+        flowWithResource {
+            studentRepository.getStudentsScrapper(
+                email = loginData!!.first,
+                password = loginData!!.second,
+                scrapperBaseUrl = loginData!!.third,
+                symbol = symbol,
+            )
+        }.onEach {
             when (it.status) {
                 Status.LOADING -> view?.run {
                     Timber.i("Login with symbol started")
@@ -98,14 +115,6 @@ class LoginSymbolPresenter @Inject constructor(
         }.launch("login")
     }
 
-    fun onParentInitSymbolView(loginData: Triple<String, String, String>) {
-        this.loginData = loginData
-        view?.apply {
-            clearAndFocusSymbol()
-            showSoftKeyboard()
-        }
-    }
-
     fun onFaqClick() {
         view?.openFaqPage()
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
index 830c77d1..75523a7c 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt
@@ -9,6 +9,8 @@ interface LoginSymbolView : BaseView {
 
     fun initView()
 
+    fun setLoginToHeading(login: String)
+
     fun setErrorSymbolIncorrect()
 
     fun setErrorSymbolRequire()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt
index 3a84b2dd..49d094b7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt
@@ -65,7 +65,7 @@ class LuckyNumberHistoryFragment :
             luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
             luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() }
 
-            luckyNumberHistoryNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
index 49a19943..2b2d18fa 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt
@@ -1,7 +1,6 @@
 package io.github.wulkanowy.ui.modules.luckynumberwidget
 
 import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT
 import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH
@@ -18,8 +17,9 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
 import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
 import io.github.wulkanowy.data.repositories.LuckyNumberRepository
 import io.github.wulkanowy.data.repositories.StudentRepository
-import io.github.wulkanowy.ui.modules.main.MainActivity
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
+import io.github.wulkanowy.utils.PendingIntentCompat
 import io.github.wulkanowy.utils.toFirstResult
 import kotlinx.coroutines.runBlocking
 import timber.log.Timber
@@ -39,6 +39,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
 
     companion object {
 
+        const val LUCKY_NUMBER_PENDING_INTENT_ID = 200
+
         fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId"
 
         fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId"
@@ -48,18 +50,31 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
         fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId"
     }
 
-    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray?) {
+    override fun onUpdate(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetIds: IntArray?
+    ) {
         super.onUpdate(context, appWidgetManager, appWidgetIds)
         appWidgetIds?.forEach { appWidgetId ->
+            val luckyNumber =
+                getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
+            val appIntent = PendingIntent.getActivity(
+                context,
+                LUCKY_NUMBER_PENDING_INTENT_ID,
+                SplashActivity.getStartIntent(context, Destination.LuckyNumber),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+            )
 
-            val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
-            val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id,
-                MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)
-
-            val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)).apply {
-                setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#")
-                setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
-            }
+            val remoteView =
+                RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context))
+                    .apply {
+                        setTextViewText(
+                            R.id.luckyNumberWidgetNumber,
+                            luckyNumber?.luckyNumber?.toString() ?: "#"
+                        )
+                        setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
+                    }
 
             setStyles(remoteView, appWidgetId)
             appWidgetManager.updateAppWidget(appWidgetId, remoteView)
@@ -78,7 +93,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
         }
     }
 
-    override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) {
+    override fun onAppWidgetOptionsChanged(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetId: Int,
+        newOptions: Bundle?
+    ) {
         super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
 
         val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context))
@@ -88,8 +108,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
     }
 
     private fun setStyles(views: RemoteViews, appWidgetId: Int, options: Bundle? = null) {
-        val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong(getWidthWidgetKey(appWidgetId), 74).toInt()
-        val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong(getHeightWidgetKey(appWidgetId), 74).toInt()
+        val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong(
+            getWidthWidgetKey(appWidgetId), 74
+        ).toInt()
+        val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong(
+            getHeightWidgetKey(appWidgetId), 74
+        ).toInt()
 
         with(sharedPref) {
             putLong(getWidthWidgetKey(appWidgetId), width.toLong())
@@ -112,7 +136,11 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
         }
     }
 
-    private fun RemoteViews.setVisibility(imageTop: Boolean, imageLeft: Boolean, title: Boolean = false) {
+    private fun RemoteViews.setVisibility(
+        imageTop: Boolean,
+        imageLeft: Boolean,
+        title: Boolean = false
+    ) {
         setViewVisibility(R.id.luckyNumberWidgetImageTop, if (imageTop) VISIBLE else GONE)
         setViewVisibility(R.id.luckyNumberWidgetImageLeft, if (imageLeft) VISIBLE else GONE)
         setViewVisibility(R.id.luckyNumberWidgetTitle, if (title) VISIBLE else GONE)
@@ -152,7 +180,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
 
     private fun getCorrectLayoutId(appWidgetId: Int, context: Context): Int {
         val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
-        val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+        val isSystemDarkMode =
+            context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
 
         return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) {
             R.layout.widget_luckynumber_dark
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
index d758ac0d..d81abe34 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt
@@ -1,20 +1,11 @@
 package io.github.wulkanowy.ui.modules.main
 
-import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
-import android.content.pm.ShortcutInfo
-import android.content.pm.ShortcutManager
-import android.graphics.drawable.Icon
-import android.os.Build
 import android.os.Build.VERSION_CODES.P
 import android.os.Bundle
 import android.view.Menu
 import android.view.MenuItem
-import androidx.annotation.RequiresApi
-import androidx.core.content.getSystemService
 import androidx.core.view.ViewCompat
 import androidx.core.view.isVisible
 import androidx.fragment.app.DialogFragment
@@ -30,19 +21,8 @@ import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.db.entities.StudentWithSemesters
 import io.github.wulkanowy.databinding.ActivityMainBinding
 import io.github.wulkanowy.ui.base.BaseActivity
+import io.github.wulkanowy.ui.modules.Destination
 import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
-import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
-import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
-import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
-import io.github.wulkanowy.ui.modules.exam.ExamFragment
-import io.github.wulkanowy.ui.modules.grade.GradeFragment
-import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
-import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
-import io.github.wulkanowy.ui.modules.message.MessageFragment
-import io.github.wulkanowy.ui.modules.more.MoreFragment
-import io.github.wulkanowy.ui.modules.note.NoteFragment
-import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
-import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
 import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.AppInfo
 import io.github.wulkanowy.utils.InAppReviewHelper
@@ -83,15 +63,14 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
         FragNavController(supportFragmentManager, R.id.main_fragment_container)
 
     companion object {
-        const val EXTRA_START_MENU = "extraStartMenu"
+
+        private const val EXTRA_START_DESTINATION = "start_destination"
 
         fun getStartIntent(
             context: Context,
-            startMenu: MainView.Section? = null,
-            clear: Boolean = false
+            destination: Destination? = null,
         ) = Intent(context, MainActivity::class.java).apply {
-            if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
-            startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
+            putExtra(EXTRA_START_DESTINATION, destination)
         }
     }
 
@@ -106,42 +85,20 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
 
     override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
 
-    override var startMenuIndex = 0
+    private var savedInstanceState: Bundle? = null
 
-    override var startMenuMoreIndex = -1
-
-    private val moreMenuFragments = mapOf<Int, Fragment>(
-        MainView.Section.MESSAGE.id to MessageFragment.newInstance(),
-        MainView.Section.EXAM.id to ExamFragment.newInstance(),
-        MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(),
-        MainView.Section.NOTE.id to NoteFragment.newInstance(),
-        MainView.Section.CONFERENCE.id to ConferenceFragment.newInstance(),
-        MainView.Section.SCHOOL_ANNOUNCEMENT.id to SchoolAnnouncementFragment.newInstance(),
-        MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance(),
-    )
-
-    @SuppressLint("NewApi")
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
         setSupportActionBar(binding.mainToolbar)
+        this.savedInstanceState = savedInstanceState
         messageContainer = binding.mainMessageContainer
         updateHelper.messageContainer = binding.mainFragmentContainer
 
-        val section = MainView.Section.values()
-            .singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) }
-
-        presenter.onAttachView(this, section)
-
-        with(navController) {
-            initialize(startMenuIndex, savedInstanceState)
-            pushFragment(moreMenuFragments[startMenuMoreIndex])
-        }
-
-        if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) {
-            initShortcuts()
-        }
+        val destination = (intent.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?)
+            ?.takeIf { savedInstanceState == null }
 
+        presenter.onAttachView(this, destination)
         updateHelper.checkAndInstallUpdates(this)
     }
 
@@ -157,71 +114,47 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
         updateHelper.onActivityResult(requestCode, resultCode)
     }
 
-    @RequiresApi(Build.VERSION_CODES.N_MR1)
-    fun initShortcuts() {
-        val shortcutsList = mutableListOf<ShortcutInfo>()
-
-        listOf(
-            Triple(
-                getString(R.string.grade_title),
-                R.drawable.ic_shortcut_grade,
-                MainView.Section.GRADE
-            ),
-            Triple(
-                getString(R.string.attendance_title),
-                R.drawable.ic_shortcut_attendance,
-                MainView.Section.ATTENDANCE
-            ),
-            Triple(
-                getString(R.string.exam_title),
-                R.drawable.ic_shortcut_exam,
-                MainView.Section.EXAM
-            ),
-            Triple(
-                getString(R.string.timetable_title),
-                R.drawable.ic_shortcut_timetable,
-                MainView.Section.TIMETABLE
-            )
-        ).forEach { (title, icon, enum) ->
-            shortcutsList.add(
-                ShortcutInfo.Builder(applicationContext, title)
-                    .setShortLabel(title)
-                    .setLongLabel(title)
-                    .setIcon(Icon.createWithResource(applicationContext, icon))
-                    .setIntents(
-                        arrayOf(
-                            Intent(applicationContext, MainActivity::class.java)
-                                .setAction(Intent.ACTION_VIEW),
-                            Intent(applicationContext, MainActivity::class.java)
-                                .putExtra(EXTRA_START_MENU, enum.id)
-                                .setAction(Intent.ACTION_VIEW)
-                                .addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
-                        )
-                    )
-                    .build()
-            )
-        }
-
-        getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
-    }
-
-    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
         menuInflater.inflate(R.menu.action_menu_main, menu)
-        accountMenu = menu?.findItem(R.id.mainMenuAccount)
+        accountMenu = menu.findItem(R.id.mainMenuAccount)
 
         presenter.onActionMenuCreated()
         return true
     }
 
-    @SuppressLint("NewApi")
-    override fun initView() {
+    override fun initView(startMenuIndex: Int, rootDestinations: List<Destination>) {
+        initializeToolbar()
+        initializeBottomNavigation(startMenuIndex)
+        initializeNavController(startMenuIndex, rootDestinations)
+    }
+
+    private fun initializeNavController(startMenuIndex: Int, rootDestinations: List<Destination>) {
+        with(navController) {
+            setOnViewChangeListener { destinationView ->
+                presenter.onViewChange(destinationView)
+                analytics.setCurrentScreen(
+                    this@MainActivity,
+                    destinationView::class.java.simpleName
+                )
+            }
+            fragmentHideStrategy = HIDE
+            rootFragments = rootDestinations.map { it.fragment }
+
+            initialize(startMenuIndex, savedInstanceState)
+        }
+        savedInstanceState = null
+    }
+
+    private fun initializeToolbar() {
         with(binding.mainToolbar) {
             stateListAnimator = null
             setBackgroundColor(
                 overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))
             )
         }
+    }
 
+    private fun initializeBottomNavigation(startMenuIndex: Int) {
         with(binding.mainBottomNav) {
             with(menu) {
                 add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title)
@@ -239,36 +172,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
             setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) }
             setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) }
         }
-
-        with(navController) {
-            setOnViewChangeListener { section, name ->
-                if (section == MainView.Section.ACCOUNT || section == MainView.Section.STUDENT_INFO) {
-                    binding.mainBottomNav.isVisible = false
-
-                    if (appInfo.systemVersion >= P) {
-                        window.navigationBarColor = getThemeAttrColor(R.attr.colorSurface)
-                    }
-                } else {
-                    binding.mainBottomNav.isVisible = true
-
-                    if (appInfo.systemVersion >= P) {
-                        window.navigationBarColor =
-                            getThemeAttrColor(android.R.attr.navigationBarColor)
-                    }
-                }
-
-                analytics.setCurrentScreen(this@MainActivity, name)
-                presenter.onViewChange(section)
-            }
-            fragmentHideStrategy = HIDE
-            rootFragments = listOf(
-                DashboardFragment.newInstance(),
-                GradeFragment.newInstance(),
-                AttendanceFragment.newInstance(),
-                TimetableFragment.newInstance(),
-                MoreFragment.newInstance()
-            )
-        }
     }
 
     override fun onPreferenceStartFragment(
@@ -317,6 +220,22 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
         ViewCompat.setElevation(binding.mainToolbar, if (show) dpToPx(4f) else 0f)
     }
 
+    override fun showBottomNavigation(show: Boolean) {
+        binding.mainBottomNav.isVisible = show
+
+        if (appInfo.systemVersion >= P) {
+            window.navigationBarColor = if (show) {
+                getThemeAttrColor(android.R.attr.navigationBarColor)
+            } else {
+                getThemeAttrColor(R.attr.colorSurface)
+            }
+        }
+    }
+
+    override fun openMoreDestination(destination: Destination) {
+        pushView(destination.fragment)
+    }
+
     override fun notifyMenuViewReselected() {
         (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected()
     }
@@ -373,6 +292,5 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         navController.onSaveInstanceState(outState)
-        intent.removeExtra(EXTRA_START_MENU)
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
index 4805b5a1..c7893153 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt
@@ -6,10 +6,15 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
 import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.services.sync.SyncManager
 import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.BaseView
 import io.github.wulkanowy.ui.base.ErrorHandler
-import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
-import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
-import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.account.AccountView
+import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView
+import io.github.wulkanowy.ui.modules.grade.GradeView
+import io.github.wulkanowy.ui.modules.message.MessageView
+import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView
+import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
 import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.flowWithResource
 import kotlinx.coroutines.flow.onEach
@@ -27,19 +32,40 @@ class MainPresenter @Inject constructor(
 
     private var studentsWitSemesters: List<StudentWithSemesters>? = null
 
-    fun onAttachView(view: MainView, initMenu: MainView.Section?) {
-        super.onAttachView(view)
-        view.apply {
-            getProperViewIndexes(initMenu).let { (main, more) ->
-                startMenuIndex = main
-                startMenuMoreIndex = more
+    private val rootDestinationTypeList = listOf(
+        Destination.Type.DASHBOARD,
+        Destination.Type.GRADE,
+        Destination.Type.ATTENDANCE,
+        Destination.Type.TIMETABLE,
+        Destination.Type.MORE
+    )
+
+    private val Destination?.startMenuIndex
+        get() = when {
+            this == null -> prefRepository.startMenuIndex
+            type in rootDestinationTypeList -> {
+                rootDestinationTypeList.indexOf(type)
             }
-            initView()
-            Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index")
+            else -> 4
+        }
+
+    fun onAttachView(view: MainView, initDestination: Destination?) {
+        super.onAttachView(view)
+
+        val startMenuIndex = initDestination.startMenuIndex
+        val destinations = rootDestinationTypeList.map {
+            if (it == initDestination?.type) initDestination else it.defaultDestination
+        }
+
+        view.initView(startMenuIndex, destinations)
+        if (initDestination != null && startMenuIndex == 4) {
+            view.openMoreDestination(initDestination)
         }
 
         syncManager.startPeriodicSyncWorker()
-        analytics.logEvent("app_open", "destination" to initMenu?.name)
+
+        analytics.logEvent("app_open", "destination" to initDestination.toString())
+        Timber.i("Main view was initialized with $initDestination")
     }
 
     fun onActionMenuCreated() {
@@ -64,9 +90,10 @@ class MainPresenter @Inject constructor(
             }.launch("avatar")
     }
 
-    fun onViewChange(section: MainView.Section?) {
+    fun onViewChange(destinationView: BaseView) {
         view?.apply {
-            showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
+            showBottomNavigation(shouldShowBottomNavigation(destinationView))
+            showActionBarElevation(shouldShowActionBarElevation(destinationView))
             currentViewTitle?.let { setViewTitle(it) }
             currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) }
             currentStackSize?.let {
@@ -76,6 +103,20 @@ class MainPresenter @Inject constructor(
         }
     }
 
+    private fun shouldShowActionBarElevation(destination: BaseView) = when (destination) {
+        is GradeView,
+        is MessageView,
+        is SchoolAndTeachersView -> false
+        else -> true
+    }
+
+    private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) {
+        is AccountView,
+        is StudentInfoView,
+        is AccountDetailsView -> false
+        else -> true
+    }
+
     fun onAccountManagerSelected(): Boolean {
         if (studentsWitSemesters.isNullOrEmpty()) return true
 
@@ -134,10 +175,4 @@ class MainPresenter @Inject constructor(
 
         view?.showStudentAvatar(currentStudent)
     }
-
-    private fun getProperViewIndexes(initMenu: MainView.Section?) = when (initMenu?.id) {
-        in 0..3 -> initMenu!!.id to -1
-        in 4..100 -> 4 to initMenu!!.id
-        else -> prefRepository.startMenuIndex to -1
-    }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
index 8851f587..3a57fcc6 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt
@@ -3,13 +3,10 @@ package io.github.wulkanowy.ui.modules.main
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.db.entities.StudentWithSemesters
 import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.Destination
 
 interface MainView : BaseView {
 
-    var startMenuIndex: Int
-
-    var startMenuMoreIndex: Int
-
     val isRootView: Boolean
 
     val currentViewTitle: String?
@@ -18,7 +15,7 @@ interface MainView : BaseView {
 
     val currentStackSize: Int?
 
-    fun initView()
+    fun initView(startMenuIndex: Int, rootDestinations: List<Destination>)
 
     fun switchMenuView(position: Int)
 
@@ -28,6 +25,8 @@ interface MainView : BaseView {
 
     fun showActionBarElevation(show: Boolean)
 
+    fun showBottomNavigation(show: Boolean)
+
     fun notifyMenuViewReselected()
 
     fun notifyMenuViewChanged()
@@ -42,6 +41,8 @@ interface MainView : BaseView {
 
     fun showInAppReview()
 
+    fun openMoreDestination(destination: Destination)
+
     interface MainChildView {
 
         fun onFragmentReselected()
@@ -57,25 +58,4 @@ interface MainView : BaseView {
             get() = ""
             set(_) {}
     }
-
-    enum class Section {
-        DASHBOARD,
-        GRADE,
-        ATTENDANCE,
-        TIMETABLE,
-        MORE,
-        MESSAGE,
-        EXAM,
-        HOMEWORK,
-        NOTE,
-        CONFERENCE,
-        SCHOOL_ANNOUNCEMENT,
-        SCHOOL,
-        LUCKY_NUMBER,
-        ACCOUNT,
-        STUDENT_INFO,
-        SETTINGS;
-
-        val id get() = ordinal
-    }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt
index 72fc627f..acf3133d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
+import com.google.android.material.tabs.TabLayoutMediator
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
@@ -26,7 +27,13 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
     @Inject
     lateinit var presenter: MessagePresenter
 
-    private val pagerAdapter by lazy { BaseFragmentPagerAdapter(childFragmentManager) }
+    private val pagerAdapter by lazy {
+        BaseFragmentPagerAdapter(
+            fragmentManager = childFragmentManager,
+            pagesCount = 3,
+            lifecycle = lifecycle,
+        )
+    }
 
     companion object {
         fun newInstance() = MessageFragment()
@@ -43,26 +50,35 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
     }
 
     override fun initView() {
-        with(pagerAdapter) {
-            containerId = binding.messageViewPager.id
-            addFragmentsWithTitle(mapOf(
-                MessageTabFragment.newInstance(RECEIVED) to getString(R.string.message_inbox),
-                MessageTabFragment.newInstance(SENT) to getString(R.string.message_sent),
-                MessageTabFragment.newInstance(TRASHED) to getString(R.string.message_trash)
-            ))
-        }
-
         with(binding.messageViewPager) {
             adapter = pagerAdapter
             offscreenPageLimit = 2
             setOnSelectPageListener(presenter::onPageSelected)
         }
 
-        with(binding.messageTabLayout) {
-            setupWithViewPager(binding.messageViewPager)
-            setElevationCompat(context.dpToPx(4f))
+        with(pagerAdapter) {
+            containerId = binding.messageViewPager.id
+            titleFactory = {
+                when (it) {
+                    0 -> getString(R.string.message_inbox)
+                    1 -> getString(R.string.message_sent)
+                    2 -> getString(R.string.message_trash)
+                    else -> throw IllegalStateException()
+                }
+            }
+            itemFactory = {
+                when (it) {
+                    0 -> MessageTabFragment.newInstance(RECEIVED)
+                    1 -> MessageTabFragment.newInstance(SENT)
+                    2 -> MessageTabFragment.newInstance(TRASHED)
+                    else -> throw IllegalStateException()
+                }
+            }
+            TabLayoutMediator(binding.messageTabLayout, binding.messageViewPager, this).attach()
         }
 
+        binding.messageTabLayout.elevation = requireContext().dpToPx(4f)
+
         binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() }
     }
 
@@ -86,7 +102,8 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
     }
 
     override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
-        (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment)?.onParentLoadData(forceRefresh)
+        (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment)
+            ?.onParentLoadData(forceRefresh)
     }
 
     override fun openSendMessage() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt
index 7b8c3d0f..9e19517b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt
@@ -15,8 +15,7 @@ class MessagePresenter @Inject constructor(
 
     override fun onAttachView(view: MessageView) {
         super.onAttachView(view)
-        launch {
-            delay(150)
+        presenterScope.launch {
             view.initView()
             Timber.i("Message view was initialized")
             loadData()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
index 74f8f57e..e1cc2e37 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewFragment.kt
@@ -1,6 +1,5 @@
 package io.github.wulkanowy.ui.modules.message.preview
 
-import android.os.Build
 import android.os.Bundle
 import android.print.PrintAttributes
 import android.print.PrintManager
@@ -13,7 +12,6 @@ import android.view.View.VISIBLE
 import android.webkit.WebResourceRequest
 import android.webkit.WebView
 import android.webkit.WebViewClient
-import androidx.annotation.RequiresApi
 import androidx.core.content.getSystemService
 import androidx.recyclerview.widget.LinearLayoutManager
 import dagger.hilt.android.AndroidEntryPoint
@@ -25,7 +23,6 @@ import io.github.wulkanowy.ui.base.BaseFragment
 import io.github.wulkanowy.ui.modules.main.MainActivity
 import io.github.wulkanowy.ui.modules.main.MainView
 import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
-import io.github.wulkanowy.utils.AppInfo
 import io.github.wulkanowy.utils.shareText
 import javax.inject.Inject
 
@@ -40,9 +37,6 @@ class MessagePreviewFragment :
     @Inject
     lateinit var previewAdapter: MessagePreviewAdapter
 
-    @Inject
-    lateinit var appInfo: AppInfo
-
     private var menuReplyButton: MenuItem? = null
 
     private var menuForwardButton: MenuItem? = null
@@ -140,7 +134,7 @@ class MessagePreviewFragment :
         menuForwardButton?.isVisible = show
         menuDeleteButton?.isVisible = show
         menuShareButton?.isVisible = show
-        menuPrintButton?.isVisible = show && appInfo.systemVersion >= Build.VERSION_CODES.LOLLIPOP
+        menuPrintButton?.isVisible = show
     }
 
     override fun setDeletedOptionsLabels() {
@@ -175,7 +169,6 @@ class MessagePreviewFragment :
         context?.shareText(text, subject)
     }
 
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     override fun printDocument(html: String, jobName: String) {
         val webView = WebView(requireContext())
         webView.webViewClient = object : WebViewClient() {
@@ -190,7 +183,6 @@ class MessagePreviewFragment :
         webView.loadDataWithBaseURL("file:///android_asset/", html, "text/HTML", "UTF-8", null)
     }
 
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     private fun createWebPrintJob(webView: WebView, jobName: String) {
         activity?.getSystemService<PrintManager>()?.let { printManager ->
             val printAdapter = webView.createPrintDocumentAdapter(jobName)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
index 702e5467..eb33ee6e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt
@@ -1,7 +1,6 @@
 package io.github.wulkanowy.ui.modules.message.preview
 
 import android.annotation.SuppressLint
-import android.os.Build
 import io.github.wulkanowy.data.Status
 import io.github.wulkanowy.data.db.entities.Message
 import io.github.wulkanowy.data.db.entities.MessageAttachment
@@ -11,7 +10,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.ui.base.BasePresenter
 import io.github.wulkanowy.ui.base.ErrorHandler
 import io.github.wulkanowy.utils.AnalyticsHelper
-import io.github.wulkanowy.utils.AppInfo
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResource
 import io.github.wulkanowy.utils.flowWithResourceIn
@@ -24,8 +22,7 @@ class MessagePreviewPresenter @Inject constructor(
     errorHandler: ErrorHandler,
     studentRepository: StudentRepository,
     private val messageRepository: MessageRepository,
-    private val analytics: AnalyticsHelper,
-    private var appInfo: AppInfo
+    private val analytics: AnalyticsHelper
 ) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
 
     var message: Message? = null
@@ -112,10 +109,11 @@ class MessagePreviewPresenter @Inject constructor(
 
     fun onShare(): Boolean {
         message?.let {
-            var text = "Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
-                true -> "Od: ${it.sender}\n"
-                false -> "Do: ${it.recipient}\n"
-            } + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
+            var text =
+                "Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
+                    true -> "Od: ${it.sender}\n"
+                    false -> "Do: ${it.recipient}\n"
+                } + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
 
             attachments?.let { attachments ->
                 if (attachments.isNotEmpty()) {
@@ -127,7 +125,10 @@ class MessagePreviewPresenter @Inject constructor(
                 }
             }
 
-            view?.shareText(text, "FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}")
+            view?.shareText(
+                text,
+                "FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}"
+            )
             return true
         }
         return false
@@ -135,7 +136,6 @@ class MessagePreviewPresenter @Inject constructor(
 
     @SuppressLint("NewApi")
     fun onPrint(): Boolean {
-        if (appInfo.systemVersion < Build.VERSION_CODES.LOLLIPOP) return false
         message?.let {
             val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
             val infoContent = "<div><h4>Data wysłania</h4>$dateString</div>" + when {
@@ -154,7 +154,9 @@ class MessagePreviewPresenter @Inject constructor(
 
             view?.apply {
                 val html = printHTML
-                    .replace("%SUBJECT%", it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
+                    .replace(
+                        "%SUBJECT%",
+                        it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
                     .replace("%CONTENT%", messageContent)
                     .replace("%INFO%", infoContent)
                 printDocument(html, jobName)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
index 583ba687..88fe77d9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewView.kt
@@ -1,7 +1,5 @@
 package io.github.wulkanowy.ui.modules.message.preview
 
-import android.os.Build
-import androidx.annotation.RequiresApi
 import io.github.wulkanowy.data.db.entities.Message
 import io.github.wulkanowy.data.db.entities.MessageWithAttachment
 import io.github.wulkanowy.ui.base.BaseView
@@ -42,8 +40,7 @@ interface MessagePreviewView : BaseView {
 
     fun shareText(text: String, subject: String)
 
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    fun printDocument(html: String, jobName: String)
-
     fun popView()
+
+    fun printDocument(html: String, jobName: String)
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt
index 26ab7f48..bd14bc89 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/RecipientChipItem.kt
@@ -1,10 +1,10 @@
 package io.github.wulkanowy.ui.modules.message.send
 
-import com.squareup.moshi.JsonClass
 import io.github.wulkanowy.data.db.entities.Recipient
 import io.github.wulkanowy.materialchipsinput.ChipItem
+import kotlinx.serialization.Serializable
 
-@JsonClass(generateAdapter = true)
+@Serializable
 data class RecipientChipItem(
 
     override val title: String,
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
index 1432a994..70f9a9b5 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt
@@ -118,7 +118,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
         presenter.onMessageContentChange()
     }
 
-    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
         menuInflater.inflate(R.menu.action_menu_send_message, menu)
         return true
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
index 60a23e58..77fa8231 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt
@@ -224,14 +224,14 @@ class SendMessagePresenter @Inject constructor(
     }
 
     fun onMessageContentChange() {
-        launch {
+        presenterScope.launch {
             messageUpdateChannel.send(Unit)
         }
     }
 
     @OptIn(FlowPreview::class)
     private fun initializeSubjectStream() {
-        launch {
+        presenterScope.launch {
             messageUpdateChannel.consumeAsFlow()
                 .debounce(250)
                 .catch { Timber.e(it) }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
index a24f9b79..f70a1bab 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt
@@ -127,10 +127,13 @@ class MessageTabPresenter @Inject constructor(
                                 onlyUnread,
                                 onlyWithAttachments
                             )
-                            val newItems = listOf(MessageTabDataItem.Header) + filteredData.map {
-                                MessageTabDataItem.MessageItem(it)
+                            val messageItems = filteredData.map { message ->
+                                MessageTabDataItem.MessageItem(message)
                             }
-                            updateData(newItems, folder.id == MessageFolder.SENT.id)
+                            val messageItemsWithHeader =
+                                listOf(MessageTabDataItem.Header) + messageItems
+
+                            updateData(messageItemsWithHeader, folder.id == MessageFolder.SENT.id)
                             notifyParentDataLoaded()
                         }
                     }
@@ -158,6 +161,9 @@ class MessageTabPresenter @Inject constructor(
                 enableSwipe(true)
                 notifyParentDataLoaded()
             }
+        }.catch {
+            errorHandler.dispatch(it)
+            view?.notifyParentDataLoaded()
         }.launch()
     }
 
@@ -168,19 +174,20 @@ class MessageTabPresenter @Inject constructor(
                 setErrorDetails(message)
                 showErrorView(true)
                 showEmpty(false)
+                showProgress(false)
             } else showError(message, error)
         }
     }
 
     fun onSearchQueryTextChange(query: String) {
-        launch {
+        presenterScope.launch {
             searchChannel.send(query)
         }
     }
 
     @OptIn(FlowPreview::class)
     private fun initializeSearchStream() {
-        launch {
+        presenterScope.launch {
             searchChannel.consumeAsFlow()
                 .debounce(250)
                 .map { query ->
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt
index 9591867d..53049891 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt
@@ -11,7 +11,6 @@ import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResource
 import io.github.wulkanowy.utils.flowWithResourceIn
-import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
 import javax.inject.Inject
@@ -148,6 +147,6 @@ class MobileDevicePresenter @Inject constructor(
                     errorHandler.dispatch(it.error!!)
                 }
             }
-        }.launchIn(this)
+        }.launch("unregister")
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt
index c441231e..10a39182 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt
@@ -11,7 +11,6 @@ import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResource
 import io.github.wulkanowy.utils.flowWithResourceIn
-import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
 import javax.inject.Inject
@@ -123,15 +122,17 @@ class NotePresenter @Inject constructor(
     }
 
     private fun updateNote(note: Note) {
-        flowWithResource { noteRepository.updateNote(note) }.onEach {
-            when (it.status) {
-                Status.LOADING -> Timber.i("Attempt to update note ${note.id}")
-                Status.SUCCESS -> Timber.i("Update note result: Success")
-                Status.ERROR -> {
-                    Timber.i("Update note result: An exception occurred")
-                    errorHandler.dispatch(it.error!!)
+        flowWithResource { noteRepository.updateNote(note) }
+            .onEach {
+                when (it.status) {
+                    Status.LOADING -> Timber.i("Attempt to update note ${note.id}")
+                    Status.SUCCESS -> Timber.i("Update note result: Success")
+                    Status.ERROR -> {
+                        Timber.i("Update note result: An exception occurred")
+                        errorHandler.dispatch(it.error!!)
+                    }
                 }
             }
-        }.launchIn(this)
+            .launch("update_note")
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt
index 7ee326f8..27b3637a 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt
@@ -5,7 +5,6 @@ import android.view.ViewGroup
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListAdapter
 import androidx.recyclerview.widget.RecyclerView
-import io.github.wulkanowy.R
 import io.github.wulkanowy.data.db.entities.Notification
 import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding
 import io.github.wulkanowy.services.sync.notifications.NotificationType
@@ -28,26 +27,12 @@ class NotificationsCenterAdapter @Inject constructor() :
             notificationsCenterItemTitle.text = item.title
             notificationsCenterItemContent.text = item.content
             notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM")
-            notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId())
+            notificationsCenterItemIcon.setImageResource(item.type.icon)
 
             root.setOnClickListener { onItemClickListener(item.type) }
         }
     }
 
-    private fun NotificationType.toDrawableResId() = when (this) {
-        NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences
-        NotificationType.NEW_EXAM -> R.drawable.ic_main_exam
-        NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade
-        NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade
-        NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade
-        NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework
-        NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber
-        NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message
-        NotificationType.NEW_NOTE -> R.drawable.ic_stat_note
-        NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about
-        NotificationType.PUSH -> R.drawable.ic_stat_all
-    }
-
     class ViewHolder(val binding: ItemNotificationsCenterBinding) :
         RecyclerView.ViewHolder(binding.root)
 
@@ -59,4 +44,4 @@ class NotificationsCenterAdapter @Inject constructor() :
         override fun areItemsTheSame(oldItem: Notification, newItem: Notification) =
             oldItem.id == newItem.id
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt
index b9bfb447..f3bbc42d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt
@@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Notification
 import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding
 import io.github.wulkanowy.services.sync.notifications.NotificationType
 import io.github.wulkanowy.ui.base.BaseFragment
+import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
 import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
 import io.github.wulkanowy.ui.modules.exam.ExamFragment
 import io.github.wulkanowy.ui.modules.grade.GradeFragment
@@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
 import io.github.wulkanowy.ui.modules.message.MessageFragment
 import io.github.wulkanowy.ui.modules.note.NoteFragment
 import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
+import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
 import javax.inject.Inject
 
 @AndroidEntryPoint
@@ -104,5 +106,7 @@ class NotificationsCenterFragment :
         NotificationType.NEW_NOTE -> NoteFragment.newInstance()
         NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance()
         NotificationType.PUSH -> null
+        NotificationType.CHANGE_TIMETABLE -> TimetableFragment.newInstance()
+        NotificationType.NEW_ATTENDANCE -> AttendanceFragment.newInstance()
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt
index c1c56961..f4fa8e01 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersFragment.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
+import com.google.android.material.tabs.TabLayoutMediator
 import dagger.hilt.android.AndroidEntryPoint
 import io.github.wulkanowy.R
 import io.github.wulkanowy.databinding.FragmentSchoolandteachersBinding
@@ -24,7 +25,13 @@ class SchoolAndTeachersFragment :
     @Inject
     lateinit var presenter: SchoolAndTeachersPresenter
 
-    private val pagerAdapter by lazy { BaseFragmentPagerAdapter(childFragmentManager) }
+    private val pagerAdapter by lazy {
+        BaseFragmentPagerAdapter(
+            fragmentManager = childFragmentManager,
+            pagesCount = 2,
+            lifecycle = lifecycle,
+        )
+    }
 
     companion object {
         fun newInstance() = SchoolAndTeachersFragment()
@@ -41,24 +48,36 @@ class SchoolAndTeachersFragment :
     }
 
     override fun initView() {
-        with(pagerAdapter) {
-            containerId = binding.schoolandteachersViewPager.id
-            addFragmentsWithTitle(mapOf(
-                SchoolFragment.newInstance() to getString(R.string.school_title),
-                TeacherFragment.newInstance() to getString(R.string.teachers_title)
-            ))
-        }
-
         with(binding.schoolandteachersViewPager) {
             adapter = pagerAdapter
             offscreenPageLimit = 2
             setOnSelectPageListener(presenter::onPageSelected)
         }
 
-        with(binding.schoolandteachersTabLayout) {
-            setupWithViewPager(binding.schoolandteachersViewPager)
-            setElevationCompat(context.dpToPx(4f))
+        with(pagerAdapter) {
+            containerId = binding.schoolandteachersViewPager.id
+            titleFactory = {
+                when (it) {
+                    0 -> getString(R.string.school_title)
+                    1 -> getString(R.string.teachers_title)
+                    else -> throw IllegalStateException()
+                }
+            }
+            itemFactory = {
+                when (it) {
+                    0 -> SchoolFragment.newInstance()
+                    1 -> TeacherFragment.newInstance()
+                    else -> throw IllegalStateException()
+                }
+            }
+            TabLayoutMediator(
+                binding.schoolandteachersTabLayout,
+                binding.schoolandteachersViewPager,
+                this
+            ).attach()
         }
+
+        binding.schoolandteachersTabLayout.elevation = requireContext().dpToPx(4f)
     }
 
     override fun showContent(show: Boolean) {
@@ -77,7 +96,8 @@ class SchoolAndTeachersFragment :
     }
 
     override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
-        (pagerAdapter.getFragmentInstance(index) as? SchoolAndTeachersChildView)?.onParentLoadData(forceRefresh)
+        (pagerAdapter.getFragmentInstance(index) as? SchoolAndTeachersChildView)
+            ?.onParentLoadData(forceRefresh)
     }
 
     override fun onDestroyView() {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt
index 915cc421..43823d6b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt
@@ -15,8 +15,7 @@ class SchoolAndTeachersPresenter @Inject constructor(
 
     override fun onAttachView(view: SchoolAndTeachersView) {
         super.onAttachView(view)
-        launch {
-            delay(150)
+        presenterScope.launch {
             view.initView()
             Timber.i("Message view was initialized")
             loadData()
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
index 202d4e5d..ac8c273e 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
 import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResourceIn
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
 import javax.inject.Inject
@@ -100,6 +101,9 @@ class SchoolPresenter @Inject constructor(
                 enableSwipe(true)
                 notifyParentDataLoaded()
             }
+        }.catch {
+            errorHandler.dispatch(it)
+            view?.notifyParentDataLoaded()
         }.launch()
     }
 
@@ -111,6 +115,7 @@ class SchoolPresenter @Inject constructor(
                 showErrorView(true)
                 showEmpty(false)
                 showContent(false)
+                showProgress(false)
             } else showError(message, error)
         }
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt
index c83cfe76..bd46ff0b 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt
@@ -9,6 +9,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
 import io.github.wulkanowy.utils.AnalyticsHelper
 import io.github.wulkanowy.utils.afterLoading
 import io.github.wulkanowy.utils.flowWithResourceIn
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.onEach
 import timber.log.Timber
 import javax.inject.Inject
@@ -85,6 +86,9 @@ class TeacherPresenter @Inject constructor(
                 enableSwipe(true)
                 notifyParentDataLoaded()
             }
+        }.catch {
+            errorHandler.dispatch(it)
+            view?.notifyParentDataLoaded()
         }.launch()
     }
 
@@ -95,6 +99,7 @@ class TeacherPresenter @Inject constructor(
                 setErrorDetails(message)
                 showErrorView(true)
                 showEmpty(false)
+                showProgress(false)
             } else showError(message, error)
         }
     }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
index 2612fab3..d56cdfa7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt
@@ -6,7 +6,7 @@ import io.github.wulkanowy.R
 import io.github.wulkanowy.ui.modules.main.MainView
 import timber.log.Timber
 
-class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
+class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, SettingsView {
 
     companion object {
 
@@ -19,4 +19,16 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
         setPreferencesFromResource(R.xml.scheme_preferences, rootKey)
         Timber.i("Settings view was initialized")
     }
+
+    override fun showError(text: String, error: Throwable) {}
+
+    override fun showMessage(text: String) {}
+
+    override fun showExpiredDialog() {}
+
+    override fun openClearLoginView() {}
+
+    override fun showErrorDetailsDialog(error: Throwable) {}
+
+    override fun showChangePasswordSnackbar(redirectUrl: String) {}
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
new file mode 100644
index 00000000..79f91bc5
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt
@@ -0,0 +1,5 @@
+package io.github.wulkanowy.ui.modules.settings
+
+import io.github.wulkanowy.ui.base.BaseView
+
+interface SettingsView : BaseView
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
index 9f29731f..a2265b04 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt
@@ -27,10 +27,6 @@ class AdvancedFragment : PreferenceFragmentCompat(),
     @Inject
     lateinit var lingver: Lingver
 
-    companion object {
-        fun newInstance() = AdvancedFragment()
-    }
-
     override val titleStringId get() = R.string.pref_settings_advanced_title
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
index a7ee800b..f603de78 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt
@@ -27,10 +27,6 @@ class AppearanceFragment : PreferenceFragmentCompat(),
     @Inject
     lateinit var lingver: Lingver
 
-    companion object {
-        fun newInstance() = AppearanceFragment()
-    }
-
     override val titleStringId get() = R.string.pref_settings_appearance_title
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
index 8470d1a5..dd6cf0ec 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt
@@ -1,6 +1,5 @@
 package io.github.wulkanowy.ui.modules.settings.notifications
 
-import android.annotation.SuppressLint
 import android.content.Intent
 import android.content.SharedPreferences
 import android.net.Uri
@@ -40,10 +39,6 @@ class NotificationsFragment : PreferenceFragmentCompat(),
     @Inject
     lateinit var appInfo: AppInfo
 
-    companion object {
-        fun newInstance() = NotificationsFragment()
-    }
-
     override val titleStringId get() = R.string.pref_settings_notifications_title
 
     override val isNotificationPermissionGranted: Boolean
@@ -156,7 +151,6 @@ class NotificationsFragment : PreferenceFragmentCompat(),
             .show()
     }
 
-    @SuppressLint("InlinedApi")
     override fun openSystemSettings() {
         val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) {
             Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
index 83caa3b0..160b7c37 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt
@@ -20,10 +20,6 @@ class SyncFragment : PreferenceFragmentCompat(),
     @Inject
     lateinit var presenter: SyncPresenter
 
-    companion object {
-        fun newInstance() = SyncFragment()
-    }
-
     override val titleStringId get() = R.string.pref_settings_sync_title
 
     override val syncSuccessString get() = getString(R.string.pref_services_message_sync_success)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt
index 63e86a47..0d404a13 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt
@@ -46,7 +46,7 @@ class SyncPresenter @Inject constructor(
     fun onSyncNowClicked() {
         view?.run {
             syncManager.startOneTimeSyncWorker().onEach { workInfo ->
-                when (workInfo.state) {
+                when (workInfo?.state) {
                     WorkInfo.State.ENQUEUED -> {
                         setSyncInProgress(true)
                         Timber.i("Setting sync now started")
@@ -63,9 +63,9 @@ class SyncPresenter @Inject constructor(
                         )
                         analytics.logEvent("sync_now", "status" to "failed")
                     }
-                    else -> Timber.d("Sync now state: ${workInfo.state}")
+                    else -> Timber.d("Sync now state: ${workInfo?.state}")
                 }
-                if (workInfo.state.isFinished) {
+                if (workInfo?.state?.isFinished == true) {
                     setSyncInProgress(false)
                     setSyncDateInView()
                 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
index 376ef374..5c152455 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashActivity.kt
@@ -1,29 +1,54 @@
 package io.github.wulkanowy.ui.modules.splash
 
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
 import android.os.Bundle
 import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
 import androidx.viewbinding.ViewBinding
 import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.services.shortcuts.ShortcutsHelper
 import io.github.wulkanowy.ui.base.BaseActivity
+import io.github.wulkanowy.ui.modules.Destination
 import io.github.wulkanowy.ui.modules.login.LoginActivity
 import io.github.wulkanowy.ui.modules.main.MainActivity
-import io.github.wulkanowy.utils.AppInfo
 import io.github.wulkanowy.utils.openInternetBrowser
 import javax.inject.Inject
 
+@SuppressLint("CustomSplashScreen")
 @AndroidEntryPoint
 class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView {
 
     @Inject
-    lateinit var appInfo: AppInfo
+    override lateinit var presenter: SplashPresenter
 
     @Inject
-    override lateinit var presenter: SplashPresenter
+    lateinit var shortcutsHelper: ShortcutsHelper
+
+    companion object {
+
+        private const val EXTRA_START_DESTINATION = "start_destination"
+
+        private const val EXTRA_EXTERNAL_URL = "external_url"
+
+        fun getStartIntent(context: Context, destination: Destination? = null) =
+            Intent(context, SplashActivity::class.java).apply {
+                putExtra(EXTRA_START_DESTINATION, destination)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+            }
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        presenter.onAttachView(this, intent?.getStringExtra("external_url"))
+        installSplashScreen().setKeepVisibleCondition { true }
+
+        val externalLink = intent?.getStringExtra(EXTRA_EXTERNAL_URL)
+        val startDestination = intent?.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?
+            ?: shortcutsHelper.getDestination(intent)
+
+        presenter.onAttachView(this, externalLink, startDestination)
     }
 
     override fun openLoginView() {
@@ -31,8 +56,8 @@ class SplashActivity : BaseActivity<SplashPresenter, ViewBinding>(), SplashView
         finish()
     }
 
-    override fun openMainView() {
-        startActivity(MainActivity.getStartIntent(this))
+    override fun openMainView(destination: Destination?) {
+        startActivity(MainActivity.getStartIntent(this, destination))
         finish()
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt
index 03e43efa..0b740902 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt
@@ -1,12 +1,10 @@
 package io.github.wulkanowy.ui.modules.splash
 
-import io.github.wulkanowy.data.Status
 import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.ui.base.BasePresenter
 import io.github.wulkanowy.ui.base.ErrorHandler
-import io.github.wulkanowy.utils.flowWithResource
-import kotlinx.coroutines.flow.onEach
-import timber.log.Timber
+import io.github.wulkanowy.ui.modules.Destination
+import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 class SplashPresenter @Inject constructor(
@@ -14,7 +12,7 @@ class SplashPresenter @Inject constructor(
     studentRepository: StudentRepository,
 ) : BasePresenter<SplashView>(errorHandler, studentRepository) {
 
-    fun onAttachView(view: SplashView, externalUrl: String?) {
+    fun onAttachView(view: SplashView, externalUrl: String?, startDestination: Destination?) {
         super.onAttachView(view)
 
         if (!externalUrl.isNullOrBlank()) {
@@ -22,15 +20,16 @@ class SplashPresenter @Inject constructor(
             return
         }
 
-        flowWithResource { studentRepository.isCurrentStudentSet() }.onEach {
-            when (it.status) {
-                Status.LOADING -> Timber.d("Is current user set check started")
-                Status.SUCCESS -> {
-                    if (it.data!!) view.openMainView()
-                    else view.openLoginView()
+        presenterScope.launch {
+            runCatching { studentRepository.isCurrentStudentSet() }
+                .onFailure(errorHandler::dispatch)
+                .onSuccess {
+                    if (it) {
+                        view.openMainView(startDestination)
+                    } else {
+                        view.openLoginView()
+                    }
                 }
-                Status.ERROR -> errorHandler.dispatch(it.error!!)
-            }
-        }.launch()
+        }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt
index a5aa1409..1c5d8bfd 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashView.kt
@@ -1,12 +1,13 @@
 package io.github.wulkanowy.ui.modules.splash
 
 import io.github.wulkanowy.ui.base.BaseView
+import io.github.wulkanowy.ui.modules.Destination
 
 interface SplashView : BaseView {
 
     fun openLoginView()
 
-    fun openMainView()
+    fun openMainView(destination: Destination?)
 
     fun openExternalUrlAndFinish(url: String)
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
index 4478a2a6..07a9f6c7 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt
@@ -97,7 +97,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
             timetableNavDate.setOnClickListener { presenter.onPickDate() }
             timetableNextButton.setOnClickListener { presenter.onNextDay() }
 
-            timetableNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            timetableNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
index 47bee1e3..e8fb9e44 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt
@@ -72,7 +72,7 @@ class AdditionalLessonsFragment :
             additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() }
             additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() }
 
-            additionalLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            additionalLessonsNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt
index 00ba0bad..36e38fb9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt
@@ -1,11 +1,13 @@
 package io.github.wulkanowy.ui.modules.timetable.completed
 
-import android.content.res.Resources
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
 import io.github.wulkanowy.ui.base.ErrorHandler
 import javax.inject.Inject
 
-class CompletedLessonsErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
+class CompletedLessonsErrorHandler @Inject constructor(@ApplicationContext context: Context) :
+    ErrorHandler(context) {
 
     var onFeatureDisabled: () -> Unit = {}
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
index b8da1c0f..a6b12644 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt
@@ -79,7 +79,7 @@ class CompletedLessonsFragment :
             completedLessonsNavDate.setOnClickListener { presenter.onPickDate() }
             completedLessonsNextButton.setOnClickListener { presenter.onNextDay() }
 
-            completedLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f))
+            completedLessonsNavContainer.elevation = requireContext().dpToPx(8f)
         }
     }
 
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
index f9079b5f..0f069116 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt
@@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.timetablewidget
 
 import android.annotation.SuppressLint
 import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_DELETED
 import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE
@@ -24,9 +23,10 @@ import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
 import io.github.wulkanowy.data.repositories.StudentRepository
 import io.github.wulkanowy.services.HiltBroadcastReceiver
 import io.github.wulkanowy.services.widgets.TimetableWidgetService
-import io.github.wulkanowy.ui.modules.main.MainActivity
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.modules.Destination
+import io.github.wulkanowy.ui.modules.splash.SplashActivity
 import io.github.wulkanowy.utils.AnalyticsHelper
+import io.github.wulkanowy.utils.PendingIntentCompat
 import io.github.wulkanowy.utils.capitalise
 import io.github.wulkanowy.utils.createNameInitialsDrawable
 import io.github.wulkanowy.utils.getCompatColor
@@ -60,6 +60,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
 
     companion object {
 
+        private const val TIMETABLE_PENDING_INTENT_ID = 201
+
         private const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
 
         private const val EXTRA_BUTTON_TYPE = "extraButtonType"
@@ -165,18 +167,20 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
                 action = appWidgetId.toString()
             }
         val accountIntent = PendingIntent.getActivity(
-            context, -Int.MAX_VALUE + appWidgetId,
+            context,
+            -Int.MAX_VALUE + appWidgetId,
             Intent(context, TimetableWidgetConfigureActivity::class.java).apply {
                 addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
                 putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
                 putExtra(EXTRA_FROM_PROVIDER, true)
-            }, FLAG_UPDATE_CURRENT
+            }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+
         )
         val appIntent = PendingIntent.getActivity(
             context,
-            MainView.Section.TIMETABLE.id,
-            MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
-            FLAG_UPDATE_CURRENT
+            TIMETABLE_PENDING_INTENT_ID,
+            SplashActivity.getStartIntent(context, Destination.Timetable()),
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
         )
 
         val remoteView = RemoteViews(context.packageName, layoutId).apply {
@@ -220,16 +224,16 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
         code: Int,
         appWidgetId: Int,
         buttonType: String
-    ): PendingIntent {
-        return PendingIntent.getBroadcast(
-            context, code,
-            Intent(context, TimetableWidgetProvider::class.java).apply {
-                action = ACTION_APPWIDGET_UPDATE
-                putExtra(EXTRA_BUTTON_TYPE, buttonType)
-                putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId)
-            }, FLAG_UPDATE_CURRENT
-        )
-    }
+    ) = PendingIntent.getBroadcast(
+        context,
+        code,
+        Intent(context, TimetableWidgetProvider::class.java).apply {
+            action = ACTION_APPWIDGET_UPDATE
+            putExtra(EXTRA_BUTTON_TYPE, buttonType)
+            putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId)
+        },
+        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
+    )
 
     private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
         val students = studentRepository.getSavedStudents(false)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt
index 0f121dc5..6b7fb4aa 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/FittedScrollableTabLayout.kt
@@ -3,17 +3,15 @@ package io.github.wulkanowy.ui.widgets
 import android.content.Context
 import android.util.AttributeSet
 import android.view.ViewGroup
+import com.google.android.material.tabs.TabLayout
 
 /**
  * @see <a href="https://stackoverflow.com/a/50382854">Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout</a>
  */
-class FittedScrollableTabLayout : MaterialTabLayout {
-
-    constructor(context: Context) : super(context)
-
-    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
-
-    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+class FittedScrollableTabLayout @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null
+) : TabLayout(context, attrs) {
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt
index a04922e5..4e1ca1a9 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialLinearLayout.kt
@@ -1,24 +1,19 @@
 package io.github.wulkanowy.ui.widgets
 
 import android.content.Context
-import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.LOLLIPOP
 import android.util.AttributeSet
 import android.widget.LinearLayout
 import androidx.core.view.ViewCompat
-import com.google.android.material.elevation.ElevationOverlayProvider
 import com.google.android.material.shape.MaterialShapeDrawable
 
-class MaterialLinearLayout : LinearLayout {
-
-    constructor(context: Context) : super(context)
-
-    constructor(context: Context, attr: AttributeSet) : super(context, attr)
-
-    constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr)
+class MaterialLinearLayout @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
 
     init {
-        val drawable = MaterialShapeDrawable.createWithElevationOverlay(context, ViewCompat.getElevation(this))
+        val drawable =
+            MaterialShapeDrawable.createWithElevationOverlay(context, ViewCompat.getElevation(this))
         ViewCompat.setBackground(this, drawable)
     }
 
@@ -28,12 +23,4 @@ class MaterialLinearLayout : LinearLayout {
             (background as MaterialShapeDrawable).elevation = elevation
         }
     }
-
-    fun setElevationCompat(elevation: Float) {
-        if (SDK_INT >= LOLLIPOP) {
-            setElevation(elevation)
-        } else {
-            setBackgroundColor(ElevationOverlayProvider(context).compositeOverlayWithThemeSurfaceColorIfNeeded(elevation))
-        }
-    }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt
deleted file mode 100644
index e19d0111..00000000
--- a/app/src/main/java/io/github/wulkanowy/ui/widgets/MaterialTabLayout.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.wulkanowy.ui.widgets
-
-import android.content.Context
-import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.LOLLIPOP
-import android.util.AttributeSet
-import com.google.android.material.elevation.ElevationOverlayProvider
-import com.google.android.material.tabs.TabLayout
-
-open class MaterialTabLayout : TabLayout {
-
-    constructor(context: Context) : super(context)
-
-    constructor(context: Context, attr: AttributeSet) : super(context, attr)
-
-    constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : super(context, attr, defStyleAttr)
-
-    fun setElevationCompat(elevation: Float) {
-        if (SDK_INT >= LOLLIPOP) {
-            setElevation(elevation)
-        } else {
-            setBackgroundColor(ElevationOverlayProvider(context).compositeOverlayWithThemeSurfaceColorIfNeeded(elevation))
-        }
-    }
-}
diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/SwipeDisabledViewPager.kt b/app/src/main/java/io/github/wulkanowy/ui/widgets/SwipeDisabledViewPager.kt
deleted file mode 100644
index eb5cae4f..00000000
--- a/app/src/main/java/io/github/wulkanowy/ui/widgets/SwipeDisabledViewPager.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.github.wulkanowy.ui.widgets
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.util.AttributeSet
-import android.view.MotionEvent
-import androidx.viewpager.widget.ViewPager
-
-class SwipeDisabledViewPager : ViewPager {
-
-    constructor(context: Context) : super(context)
-
-    constructor(context: Context, attr: AttributeSet) : super(context, attr)
-
-    @SuppressLint("ClickableViewAccessibility")
-    override fun onTouchEvent(ev: MotionEvent) = false
-
-    override fun onInterceptTouchEvent(ev: MotionEvent) = false
-}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
index a3961aed..962e5b20 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt
@@ -1,35 +1,31 @@
 package io.github.wulkanowy.utils
 
 import android.content.res.Resources
-import android.os.Build.MANUFACTURER
-import android.os.Build.MODEL
-import android.os.Build.VERSION.SDK_INT
-import io.github.wulkanowy.BuildConfig.BUILD_TIMESTAMP
-import io.github.wulkanowy.BuildConfig.DEBUG
-import io.github.wulkanowy.BuildConfig.FLAVOR
-import io.github.wulkanowy.BuildConfig.VERSION_CODE
-import io.github.wulkanowy.BuildConfig.VERSION_NAME
+import android.os.Build
+import io.github.wulkanowy.BuildConfig
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
 open class AppInfo @Inject constructor() {
 
-    open val isDebug get() = DEBUG
+    open val isDebug get() = BuildConfig.DEBUG
 
-    open val versionCode get() = VERSION_CODE
+    open val versionCode get() = BuildConfig.VERSION_CODE
 
-    open val buildTimestamp get() = BUILD_TIMESTAMP
+    open val buildTimestamp get() = BuildConfig.BUILD_TIMESTAMP
 
-    open val buildFlavor get() = FLAVOR
+    open val buildFlavor get() = BuildConfig.FLAVOR
 
-    open val versionName get() = VERSION_NAME
+    open val versionName get() = BuildConfig.VERSION_NAME
 
-    open val systemVersion get() = SDK_INT
+    open val systemVersion get() = Build.VERSION.SDK_INT
 
-    open val systemManufacturer: String get() = MANUFACTURER
+    open val systemManufacturer: String get() = Build.MANUFACTURER
 
-    open val systemModel: String get() = MODEL
+    open val systemModel: String get() = Build.MODEL
+
+    open val messagesBaseUrl = BuildConfig.MESSAGES_BASE_URL
 
     @Suppress("DEPRECATION")
     open val systemLanguage: String
diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
index 479cc518..397c9595 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt
@@ -17,7 +17,7 @@ private inline val AttendanceSummary.allAbsences: Double
     get() = absence.toDouble() + absenceExcused
 
 inline val Attendance.isExcusableOrNotExcused: Boolean
-    get() = excusable || ((absence || lateness) && !excused)
+    get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
 
 fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
 
@@ -29,7 +29,7 @@ private fun calculatePercentage(presence: Double, absence: Double): Double {
     return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100
 }
 
-inline val Attendance.description
+inline val Attendance.descriptionRes
     get() = when (AttendanceCategory.getCategoryByName(name)) {
         AttendanceCategory.PRESENCE -> R.string.attendance_present
         AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
index 2cd4459e..ecd982a1 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt
@@ -18,6 +18,7 @@ import androidx.annotation.AttrRes
 import androidx.annotation.ColorInt
 import androidx.annotation.ColorRes
 import androidx.annotation.DrawableRes
+import androidx.annotation.PluralsRes
 import androidx.core.content.ContextCompat
 import androidx.core.graphics.ColorUtils
 import androidx.core.graphics.applyCanvas
@@ -57,6 +58,9 @@ fun Context.getCompatDrawable(@DrawableRes drawableRes: Int, @ColorRes colorRes:
 fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: Int) =
     getCompatDrawable(drawableRes, colorRes)?.toBitmap()
 
+fun Context.getPlural(@PluralsRes pluralRes: Int, quantity: Int, vararg arguments: Any) =
+    resources.getQuantityString(pluralRes, quantity, *arguments)
+
 fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) {
     Intent.parseUri(uri, 0).let {
         try {
diff --git a/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt b/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt
index ecc8e05e..8aaa57f4 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/DispatchersProvider.kt
@@ -1,10 +1,8 @@
 package io.github.wulkanowy.utils
 
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 
 open class DispatchersProvider {
 
-    open val backgroundThread: CoroutineDispatcher
-        get() = Dispatchers.IO
+    open val io get() = Dispatchers.IO
 }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt
index 9dc1e18a..01c876dd 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt
@@ -2,16 +2,19 @@ package io.github.wulkanowy.utils
 
 import androidx.fragment.app.Fragment
 import com.ncapdevi.fragnav.FragNavController
-import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.ui.base.BaseView
 
-inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?, name: String?) -> Unit) {
+inline fun FragNavController.setOnViewChangeListener(crossinline listener: (view: BaseView) -> Unit) {
     transactionListener = object : FragNavController.TransactionListener {
-        override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) {
-            listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName })
+        override fun onFragmentTransaction(
+            fragment: Fragment?,
+            transactionType: FragNavController.TransactionType
+        ) {
+            fragment?.let { listener(it as BaseView) }
         }
 
         override fun onTabTransaction(fragment: Fragment?, index: Int) {
-            listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName })
+            fragment?.let { listener(it as BaseView) }
         }
     }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt
deleted file mode 100644
index 210a6209..00000000
--- a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package io.github.wulkanowy.utils
-
-import androidx.fragment.app.Fragment
-import io.github.wulkanowy.ui.modules.account.AccountFragment
-import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
-import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
-import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
-import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
-import io.github.wulkanowy.ui.modules.exam.ExamFragment
-import io.github.wulkanowy.ui.modules.grade.GradeFragment
-import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
-import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
-import io.github.wulkanowy.ui.modules.main.MainView
-import io.github.wulkanowy.ui.modules.message.MessageFragment
-import io.github.wulkanowy.ui.modules.more.MoreFragment
-import io.github.wulkanowy.ui.modules.note.NoteFragment
-import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
-import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
-import io.github.wulkanowy.ui.modules.settings.SettingsFragment
-import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
-import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
-
-fun Fragment.toSection(): MainView.Section? {
-    return when (this) {
-        is GradeFragment -> MainView.Section.GRADE
-        is AttendanceFragment -> MainView.Section.ATTENDANCE
-        is ExamFragment -> MainView.Section.EXAM
-        is TimetableFragment -> MainView.Section.TIMETABLE
-        is MoreFragment -> MainView.Section.MORE
-        is MessageFragment -> MainView.Section.MESSAGE
-        is HomeworkFragment -> MainView.Section.HOMEWORK
-        is NoteFragment -> MainView.Section.NOTE
-        is LuckyNumberFragment -> MainView.Section.LUCKY_NUMBER
-        is SettingsFragment -> MainView.Section.SETTINGS
-        is SchoolAndTeachersFragment -> MainView.Section.SCHOOL
-        is AccountFragment -> MainView.Section.ACCOUNT
-        is AccountDetailsFragment -> MainView.Section.ACCOUNT
-        is StudentInfoFragment -> MainView.Section.STUDENT_INFO
-        is ConferenceFragment -> MainView.Section.CONFERENCE
-        is SchoolAnnouncementFragment -> MainView.Section.SCHOOL_ANNOUNCEMENT
-        is DashboardFragment -> MainView.Section.DASHBOARD
-        else -> null
-    }
-}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
index d2a8908c..032e2d28 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt
@@ -4,13 +4,12 @@ import android.os.Handler
 import android.os.Looper
 import androidx.appcompat.app.AppCompatActivity
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import kotlin.properties.ReadWriteProperty
 import kotlin.reflect.KProperty
 
-class LifecycleAwareVariable<T : Any> : ReadWriteProperty<Fragment, T>, LifecycleObserver {
+class LifecycleAwareVariable<T : Any> : ReadWriteProperty<Fragment, T>, DefaultLifecycleObserver {
 
     private var _value: T? = null
 
@@ -23,15 +22,15 @@ class LifecycleAwareVariable<T : Any> : ReadWriteProperty<Fragment, T>, Lifecycl
     override fun getValue(thisRef: Fragment, property: KProperty<*>) = _value
         ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized")
 
-    @Suppress("unused")
-    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-    fun onDestroyView() {
-        _value = null
+    override fun onDestroy(owner: LifecycleOwner) {
+        Handler(Looper.getMainLooper()).post {
+            _value = null
+        }
     }
 }
 
 class LifecycleAwareVariableActivity<T : Any> : ReadWriteProperty<AppCompatActivity, T>,
-    LifecycleObserver {
+    DefaultLifecycleObserver {
 
     private var _value: T? = null
 
@@ -44,9 +43,7 @@ class LifecycleAwareVariableActivity<T : Any> : ReadWriteProperty<AppCompatActiv
     override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>) = _value
         ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized")
 
-    @Suppress("unused")
-    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-    fun onDestroyView() {
+    override fun onDestroy(owner: LifecycleOwner) {
         Handler(Looper.getMainLooper()).post {
             _value = null
         }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt b/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt
new file mode 100644
index 00000000..45ee431a
--- /dev/null
+++ b/app/src/main/java/io/github/wulkanowy/utils/PendingIntentCompat.kt
@@ -0,0 +1,11 @@
+package io.github.wulkanowy.utils
+
+import android.app.PendingIntent
+import android.os.Build
+
+object PendingIntentCompat {
+
+    val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+        PendingIntent.FLAG_IMMUTABLE
+    } else 0
+}
diff --git a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
index 5c888f30..bddd7df4 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt
@@ -4,6 +4,4 @@ inline fun String?.ifNullOrBlank(defaultValue: () -> String) =
     if (isNullOrBlank()) defaultValue() else this
 
 fun String.capitalise() =
-    replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
-
-fun String.decapitalise() = replaceFirstChar { it.lowercase() }
+    replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
\ No newline at end of file
diff --git a/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt
index 6a5ad880..700ac2f1 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/ViewPagerExtension.kt
@@ -1,13 +1,11 @@
 package io.github.wulkanowy.utils
 
-import androidx.viewpager.widget.ViewPager
+import androidx.viewpager2.widget.ViewPager2
 
-inline fun ViewPager.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) {
-    addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
+inline fun ViewPager2.setOnSelectPageListener(crossinline selectListener: (position: Int) -> Unit) {
+    registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
         override fun onPageSelected(position: Int) {
             selectListener(position)
         }
-        override fun onPageScrollStateChanged(state: Int) {}
-        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
     })
 }
diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
index 74ae1932..c994ebab 100644
--- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
+++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt
@@ -2,10 +2,8 @@
 
 package io.github.wulkanowy.utils.security
 
-import android.annotation.TargetApi
 import android.content.Context
 import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
 import android.os.Build.VERSION_CODES.M
 import android.security.KeyPairGeneratorSpec
 import android.security.keystore.KeyGenParameterSpec
@@ -116,7 +114,6 @@ fun decrypt(cipherText: String): String {
     }
 }
 
-@TargetApi(JELLY_BEAN_MR2)
 private fun generateKeyPair(context: Context) {
     (if (SDK_INT >= M) {
         KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
diff --git a/app/src/main/play/listings/cs-CZ/full-description.txt b/app/src/main/play/listings/cs-CZ/full-description.txt
new file mode 100644
index 00000000..1420f5d6
--- /dev/null
+++ b/app/src/main/play/listings/cs-CZ/full-description.txt
@@ -0,0 +1,14 @@
+Aplikace je určena pro uživatele deníku VULCAN UONET+.
+
+Zvýrazněné vlastnosti a funkce:
+- výpočet váženého průměru,
+- procentuální zobrazení docházky,
+- šťastné číslo,
+- náhled na další a dokončené lekce,
+- tmavý motiv,
+- žádné reklamy,
+- offline režim,
+- upozornění.
+
+GitHub: https://github.com/wulkanowy/wulkanowy
+Discord: https://discord.gg/vccAQBr
diff --git a/app/src/main/play/listings/cs-CZ/short-description.txt b/app/src/main/play/listings/cs-CZ/short-description.txt
new file mode 100644
index 00000000..0f29ab1b
--- /dev/null
+++ b/app/src/main/play/listings/cs-CZ/short-description.txt
@@ -0,0 +1 @@
+Neoficiální aplikace žáka a rodiče pro deníku VULCAN UONET+
diff --git a/app/src/main/play/listings/cs-CZ/title.txt b/app/src/main/play/listings/cs-CZ/title.txt
new file mode 100644
index 00000000..b7f42a5b
--- /dev/null
+++ b/app/src/main/play/listings/cs-CZ/title.txt
@@ -0,0 +1 @@
+Wulkanowy Deníček
diff --git a/app/src/main/play/listings/sk/full-description.txt b/app/src/main/play/listings/sk/full-description.txt
new file mode 100644
index 00000000..2a4787d2
--- /dev/null
+++ b/app/src/main/play/listings/sk/full-description.txt
@@ -0,0 +1,14 @@
+Aplikácia je určená pre užívateľov denníka VULCAN UONET+.
+
+Zvýraznené vlastnosti a funkcie:
+- výpočet váženého priemeru,
+- percentuálne zobrazenie dochádzky,
+- šťastné číslo,
+- náhľad na ďalšie a dokončené lekcie,
+- tmavý motív,
+- žiadne reklamy,
+- offline režim,
+- upozornenia.
+
+GitHub: https://github.com/wulkanowy/wulkanowy
+Discord: https://discord.gg/vccAQBr
diff --git a/app/src/main/play/listings/sk/short-description.txt b/app/src/main/play/listings/sk/short-description.txt
new file mode 100644
index 00000000..645ebbb6
--- /dev/null
+++ b/app/src/main/play/listings/sk/short-description.txt
@@ -0,0 +1 @@
+Neoficiálna aplikácia žiaka a rodiča pre denníka VULCAN UONET+
diff --git a/app/src/main/play/listings/sk/title.txt b/app/src/main/play/listings/sk/title.txt
new file mode 100644
index 00000000..aa50ce77
--- /dev/null
+++ b/app/src/main/play/listings/sk/title.txt
@@ -0,0 +1 @@
+Wulkanowy Denníček
diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt
index fc9fab88..5c809cfa 100644
--- a/app/src/main/play/release-notes/pl-PL/default.txt
+++ b/app/src/main/play/release-notes/pl-PL/default.txt
@@ -1,9 +1,10 @@
-Wersja 1.3.0
+Wersja 1.4.0
 
-- naprawiliśmy logowanie na platformę Opolskiej eSzkoły
-- dodaliśmy centrum powiadomień i opcję odbierania pushy z oficjalnej aplikacji (dla zaawansowanych)
-- dodaliśmy objaśnienie do informacji o obliczonych średnich w podsumowaniu
-- poprawiliśmy wyświetlanie zmian w planie lekcji
+- dodaliśmy możliwość dodawania własnych zadań domowych
+- dodaliśmy kafelek z wiadomościami od twórców
+- dodaliśmy dodatkowy tryb rozwijania szczegółów ocen
+- dodaliśmy wsparcie dla Androida 12
+- ulepszyliśmy powiadomienia na Mi Bandzie
 - dokonaliśmy też kilka innych zmian i kosmetycznych poprawek poprawiających komfort używania aplikacji
 
 Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
diff --git a/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml b/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml
new file mode 100644
index 00000000..dad56a17
--- /dev/null
+++ b/app/src/main/res/drawable-v23/compat_splash_screen_no_icon_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:gravity="fill">
+        <color android:color="?attr/windowSplashScreenBackground" />
+    </item>
+    <item
+        android:width="@dimen/splashscreen_icon_size_no_background"
+        android:height="@dimen/splashscreen_icon_size_no_background"
+        android:drawable="?windowSplashScreenAnimatedIcon"
+        android:gravity="center" />
+
+    <!-- We mask the outer bounds of the icon like we do on Android 12 -->
+    <item
+        android:width="@dimen/splashscreen_icon_mask_size_no_background"
+        android:height="@dimen/splashscreen_icon_mask_size_no_background"
+        android:gravity="center">
+        <shape android:shape="oval">
+            <stroke
+                android:width="@dimen/splashscreen_icon_mask_stroke_no_background"
+                android:color="?windowSplashScreenBackground" />
+            <solid android:color="@android:color/transparent" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/app/src/main/res/drawable-v23/img_splash_logo.png b/app/src/main/res/drawable-v23/img_splash_logo.png
deleted file mode 100644
index 61489d81..00000000
Binary files a/app/src/main/res/drawable-v23/img_splash_logo.png and /dev/null differ
diff --git a/app/src/main/res/drawable-v23/layer_splash_background.xml b/app/src/main/res/drawable-v23/layer_splash_background.xml
deleted file mode 100644
index 1b4b64ec..00000000
--- a/app/src/main/res/drawable-v23/layer_splash_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <shape>
-            <solid android:color="@color/colorPrimaryDark" />
-        </shape>
-    </item>
-    <item
-        android:width="200dp"
-        android:height="200dp"
-        android:drawable="@drawable/img_splash_logo"
-        android:gravity="center" />
-</layer-list>
diff --git a/app/src/main/res/drawable/ic_dashboard_warning.xml b/app/src/main/res/drawable/ic_dashboard_warning.xml
new file mode 100644
index 00000000..e7a5dc5a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dashboard_warning.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,5.99L19.53,19L4.47,19L12,5.99M2.74,18c-0.77,1.33 0.19,3 1.73,3h15.06c1.54,0 2.5,-1.67 1.73,-3L13.73,4.99c-0.77,-1.33 -2.69,-1.33 -3.46,0L2.74,18zM11,11v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM11,16h2v2h-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_settings_ads.xml b/app/src/main/res/drawable/ic_settings_ads.xml
new file mode 100644
index 00000000..c333ea76
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_ads.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorOnSurface"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,11c0,0.67 0,1.33 0,2c1.2,0 2.76,0 4,0c0,-0.67 0,-1.33 0,-2C20.76,11 19.2,11 18,11z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M16,17.61c0.96,0.71 2.21,1.65 3.2,2.39c0.4,-0.53 0.8,-1.07 1.2,-1.6c-0.99,-0.74 -2.24,-1.68 -3.2,-2.4C16.8,16.54 16.4,17.08 16,17.61z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20.4,5.6C20,5.07 19.6,4.53 19.2,4c-0.99,0.74 -2.24,1.68 -3.2,2.4c0.4,0.53 0.8,1.07 1.2,1.6C18.16,7.28 19.41,6.35 20.4,5.6z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M4,9c-1.1,0 -2,0.9 -2,2v2c0,1.1 0.9,2 2,2h1v4h2v-4h1l5,3V6L8,9H4zM9.03,10.71L11,9.53v4.94l-1.97,-1.18L8.55,13H8H4v-2h4h0.55L9.03,10.71z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.5,12c0,-1.33 -0.58,-2.53 -1.5,-3.35v6.69C14.92,14.53 15.5,13.33 15.5,12z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_splash_logo.xml b/app/src/main/res/drawable/ic_splash_logo.xml
new file mode 100644
index 00000000..e2e74731
--- /dev/null
+++ b/app/src/main/res/drawable/ic_splash_logo.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="1926"
+    android:viewportHeight="1926">
+    <path
+        android:fillColor="#fff"
+        android:pathData="M1082,1260v4l-6,73c0,5 -5,9 -10,9H879c-4,0 -8,-2 -10,-5l-31,-58 -29,-71v-8l48,-94c2,-3 2,-5 1, -8l-24,-65c-1,-2 0,-5 1,-7l39,-84c4,-8 17,-8 20,1l12,38 38,101c1,2 3,5 6,6l80,27c3,1 6,3 7,6l45,135zM852,717c0,-23 15,-43 35,-52l-2,-8c0,-11 13,-20 29,-20h1a38,38 0,0 1,36 -18l3,-2c11,-16 37,-27 68,-27 12,0 23,2 32,4 3,-3 8,-6 14,-6s12,3 14,8c8,-9 22,-16 38,-16 25,0 45,16 45,36l-1,7 3,4c16,6 27,17 27,30 0,14 -15,27 -34,32 -2,0 -3,1 -3,3 0,11 -11,21 -26,22v1c0,22 -41,40 -92,40 -11,0 -21,-1 -31,-3v1c0,9 -12,16 -26,16h-2l2,7c0,14 -18,25 -40,25h-7l-4, 2c-2,7 -8,12 -15,12 -9,0 -16,-7 -16,-16 0,-5 1,-8 4,-11l1,-4c-2,-3 -2,-5 -2,-8 0,-2 -2,-3 -3,-3 -27,-6 -48,-29 -48,-56z" />
+    <path
+        android:fillColor="#fff"
+        android:pathData="M1336,1346h-125c-3,0 -6,-1 -8,-4l-84,-118 -1,-2 -42,-122 -5,-5 -102,-52 -5,-6 -15,-75 -2,-3 -34,-51c-2,-3 -2,-6 -1,-9l21,-45c2,-4 5,-6 9,-6l39,-2c3,0 5,-1 6,-3l29,-22c5,-4 14,-2 17,4l98,201 1,2 13,81 2,5 197,216c6,6 1,16 -8,16zM801,1302c1,3 1,6 -1,8l-19,31c-2,3 -5,5 -9,5H591c-8,0 -13,-8 -9,-15l116,-171 2,-3 53, -228c1,-3 3,-5 5,-6l72,-40c3,-1 5,-4 5,-7l10,-42c2,-9 15,-11 20,-3l3,6c2,2 2,5 1,8l-66,190v5l16,91v5l-40,88v6l22,72z" />
+</vector>
diff --git a/app/src/main/res/drawable/img_splash_logo.png b/app/src/main/res/drawable/img_splash_logo.png
deleted file mode 100644
index fb521bf6..00000000
Binary files a/app/src/main/res/drawable/img_splash_logo.png and /dev/null differ
diff --git a/app/src/main/res/drawable/layer_splash_background.xml b/app/src/main/res/drawable/layer_splash_background.xml
deleted file mode 100644
index 2cf46d1d..00000000
--- a/app/src/main/res/drawable/layer_splash_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <shape>
-            <solid android:color="@color/colorPrimaryDark" />
-        </shape>
-    </item>
-    <item>
-        <bitmap
-            android:gravity="left|right|top|bottom"
-            android:src="@drawable/img_splash_logo" />
-    </item>
-</layer-list>
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index e55ea8b9..1d5b5280 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -10,7 +10,7 @@
         android:layout_height="wrap_content"
         android:background="@android:color/transparent" />
 
-    <io.github.wulkanowy.ui.widgets.SwipeDisabledViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/loginViewpager"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
diff --git a/app/src/main/res/layout/dialog_error.xml b/app/src/main/res/layout/dialog_error.xml
index a78790bc..b52c99ac 100644
--- a/app/src/main/res/layout/dialog_error.xml
+++ b/app/src/main/res/layout/dialog_error.xml
@@ -4,7 +4,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minWidth="300dp"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    tools:context=".ui.base.ErrorDialog">
 
     <TextView
         android:layout_width="match_parent"
@@ -16,7 +17,7 @@
         android:textStyle="bold" />
 
     <TextView
-        android:id="@+id/errorDialogMessage"
+        android:id="@+id/errorDialogHumanizedMessage"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingHorizontal="20dp"
@@ -24,6 +25,17 @@
         android:textSize="21sp"
         tools:text="@tools:sample/lorem" />
 
+    <TextView
+        android:id="@+id/errorDialogErrorMessage"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="5dp"
+        android:paddingHorizontal="20dp"
+        android:paddingTop="10dp"
+        android:textIsSelectable="true"
+        android:textColor="?android:textColorSecondary"
+        tools:text="@tools:sample/lorem" />
+
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="0dp"
@@ -51,6 +63,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:gravity="start"
+                    android:textColor="?android:textColorTertiary"
                     android:textIsSelectable="true"
                     android:textSize="12sp"
                     tools:text="@tools:sample/lorem/random" />
diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml
index 22a03cb2..341cec54 100644
--- a/app/src/main/res/layout/dialog_homework.xml
+++ b/app/src/main/res/layout/dialog_homework.xml
@@ -4,6 +4,7 @@
     android:id="@+id/parent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:minWidth="300dp"
     android:orientation="vertical">
 
     <androidx.recyclerview.widget.RecyclerView
@@ -17,8 +18,8 @@
 
     <View
         android:layout_width="match_parent"
-        android:background="@drawable/ic_all_divider"
-        android:layout_height="1dp" />
+        android:layout_height="1dp"
+        android:background="@drawable/ic_all_divider" />
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
@@ -32,6 +33,9 @@
             style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
             android:layout_width="wrap_content"
             android:layout_height="36dp"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentBottom="true"
+            android:layout_gravity="center_vertical"
             android:layout_marginTop="8dp"
             android:layout_marginEnd="8dp"
             android:layout_marginBottom="8dp"
@@ -40,9 +44,6 @@
             android:insetRight="0dp"
             android:insetBottom="0dp"
             android:minWidth="88dp"
-            android:layout_gravity="center_vertical"
-            android:layout_alignParentStart="true"
-            android:layout_alignParentBottom="true"
             android:text="@string/homework_mark_as_done"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent" />
@@ -52,6 +53,9 @@
             style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
             android:layout_width="wrap_content"
             android:layout_height="36dp"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentBottom="true"
+            android:layout_gravity="center_vertical"
             android:layout_marginTop="8dp"
             android:layout_marginEnd="8dp"
             android:layout_marginBottom="8dp"
@@ -60,9 +64,6 @@
             android:insetRight="0dp"
             android:insetBottom="0dp"
             android:minWidth="88dp"
-            android:layout_gravity="center_vertical"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentBottom="true"
             android:text="@string/all_close"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent" />
diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml
new file mode 100644
index 00000000..b9b8d1a2
--- /dev/null
+++ b/app/src/main/res/layout/dialog_homework_add.xml
@@ -0,0 +1,134 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true"
+    android:minWidth="300dp"
+    android:paddingStart="24dp"
+    android:paddingEnd="24dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minWidth="300dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/addHomeworkHeader"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginTop="24dp"
+            android:layout_marginEnd="24dp"
+            android:text="@string/homework_add"
+            android:textSize="21sp"
+            android:textStyle="bold" />
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/homeworkDialogDate"
+            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginTop="28dp"
+            android:hint="@string/all_date"
+            app:startIconDrawable="@drawable/ic_main_timetable">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/homeworkDialogDateEdit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clickable="false"
+                android:editable="false"
+                android:focusable="false"
+                android:inputType="none"
+                tools:ignore="Deprecated" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/homeworkDialogSubject"
+            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginTop="16dp"
+            android:hint="@string/all_subject">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/homeworkDialogSubjectEdit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/homeworkDialogTeacher"
+            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginTop="16dp"
+            android:hint="@string/all_teacher">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/homeworkDialogTeacherEdit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/homeworkDialogContent"
+            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:hint="@string/all_content">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/homeworkDialogContentEdit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:minHeight="62dp">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/homeworkDialogClose"
+                style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
+                android:layout_width="wrap_content"
+                android:layout_height="36dp"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="0dp"
+                android:layout_marginLeft="0dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="8dp"
+                android:layout_marginBottom="8dp"
+                android:insetLeft="0dp"
+                android:insetTop="0dp"
+                android:insetRight="0dp"
+                android:insetBottom="0dp"
+                android:text="@string/all_close"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/homeworkDialogAdd"
+                style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
+                android:layout_width="wrap_content"
+                android:layout_height="36dp"
+                android:layout_marginBottom="8dp"
+                android:insetLeft="0dp"
+                android:insetTop="0dp"
+                android:insetRight="0dp"
+                android:insetBottom="0dp"
+                android:text="@string/all_add"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml
index 8016081b..4996b85d 100644
--- a/app/src/main/res/layout/fragment_attendance.xml
+++ b/app/src/main/res/layout/fragment_attendance.xml
@@ -1,14 +1,15 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:orientation="vertical"
     tools:context=".ui.modules.attendance.AttendanceFragment">
 
     <androidx.coordinatorlayout.widget.CoordinatorLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginBottom="50dp">
+        android:layout_height="0dp"
+        android:layout_weight="1">
 
         <com.google.android.material.progressindicator.CircularProgressIndicator
             android:id="@+id/attendanceProgress"
@@ -142,8 +143,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/attendanceNavDate"
@@ -167,7 +168,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
-</FrameLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_exam.xml b/app/src/main/res/layout/fragment_exam.xml
index ca88849c..0c62aab5 100644
--- a/app/src/main/res/layout/fragment_exam.xml
+++ b/app/src/main/res/layout/fragment_exam.xml
@@ -128,8 +128,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/examNavDate"
@@ -152,7 +152,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </FrameLayout>
diff --git a/app/src/main/res/layout/fragment_grade.xml b/app/src/main/res/layout/fragment_grade.xml
index ed0447fb..989929d4 100644
--- a/app/src/main/res/layout/fragment_grade.xml
+++ b/app/src/main/res/layout/fragment_grade.xml
@@ -17,7 +17,7 @@
         tools:ignore="UnusedAttribute"
         tools:visibility="visible" />
 
-    <androidx.viewpager.widget.ViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/gradeViewPager"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/app/src/main/res/layout/fragment_homework.xml b/app/src/main/res/layout/fragment_homework.xml
index c0b5698d..ae8270ab 100644
--- a/app/src/main/res/layout/fragment_homework.xml
+++ b/app/src/main/res/layout/fragment_homework.xml
@@ -26,6 +26,8 @@
                 android:id="@+id/homeworkRecycler"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
+                android:clipToPadding="false"
+                android:paddingBottom="64dp"
                 tools:listitem="@layout/item_homework" />
         </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
 
@@ -105,6 +107,18 @@
                     android:text="@string/all_retry" />
             </LinearLayout>
         </LinearLayout>
+
+        <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+            android:id="@+id/openAddHomeworkButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|end"
+            android:layout_margin="16dp"
+            android:clickable="true"
+            android:focusable="true"
+            android:text="@string/add_homework_title"
+            android:tint="?colorOnSecondary"
+            app:icon="@drawable/ic_all_add" />
     </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
     <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
@@ -128,8 +142,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/homeworkNavDate"
@@ -152,7 +166,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </FrameLayout>
diff --git a/app/src/main/res/layout/fragment_lucky_number_history.xml b/app/src/main/res/layout/fragment_lucky_number_history.xml
index 5f50d126..a5698e2e 100644
--- a/app/src/main/res/layout/fragment_lucky_number_history.xml
+++ b/app/src/main/res/layout/fragment_lucky_number_history.xml
@@ -118,8 +118,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/luckyNumberHistoryNavDate"
@@ -143,7 +143,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_message.xml b/app/src/main/res/layout/fragment_message.xml
index a61f3738..5269d95e 100644
--- a/app/src/main/res/layout/fragment_message.xml
+++ b/app/src/main/res/layout/fragment_message.xml
@@ -5,7 +5,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <io.github.wulkanowy.ui.widgets.MaterialTabLayout
+    <com.google.android.material.tabs.TabLayout
         android:id="@+id/messageTabLayout"
         android:layout_width="match_parent"
         android:layout_height="48dp"
@@ -20,7 +20,7 @@
         tools:ignore="UnusedAttribute"
         tools:visibility="visible" />
 
-    <androidx.viewpager.widget.ViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/messageViewPager"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/app/src/main/res/layout/fragment_school.xml b/app/src/main/res/layout/fragment_school.xml
index 32be61db..3f5dc6e2 100644
--- a/app/src/main/res/layout/fragment_school.xml
+++ b/app/src/main/res/layout/fragment_school.xml
@@ -15,19 +15,18 @@
     <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
         android:id="@+id/schoolSwipe"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingStart="8dp"
-        android:paddingLeft="8dp"
-        android:paddingTop="8dp"
-        android:paddingEnd="12dp"
-        android:paddingRight="12dp"
-        android:paddingBottom="8dp">
+        android:layout_height="match_parent">
 
         <LinearLayout
             android:id="@+id/schoolContent"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:orientation="vertical">
+            android:orientation="vertical"
+            android:paddingVertical="8dp"
+            android:paddingStart="8dp"
+            android:paddingEnd="12dp"
+            android:visibility="invisible"
+            tools:visibility="visible">
 
             <LinearLayout
                 android:layout_width="match_parent"
@@ -60,6 +59,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="horizontal">
+
                 <LinearLayout
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
@@ -92,21 +92,22 @@
                     android:id="@+id/schoolAddressButton"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    app:srcCompat="@drawable/ic_school_directions"
-                    android:contentDescription="@string/school_address_button"
-                    android:background="?attr/selectableItemBackgroundBorderless"
-                    android:tint="?colorPrimary"
-                    android:padding="4dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginLeft="8dp"
                     android:layout_marginTop="8dp"
                     android:layout_marginRight="8dp"
-                    android:layout_marginLeft="8dp"
-                    android:layout_gravity="center_vertical" />
+                    android:background="?attr/selectableItemBackgroundBorderless"
+                    android:contentDescription="@string/school_address_button"
+                    android:padding="4dp"
+                    app:srcCompat="@drawable/ic_school_directions"
+                    app:tint="?colorPrimary" />
             </LinearLayout>
 
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="horizontal">
+
                 <LinearLayout
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
@@ -139,15 +140,15 @@
                     android:id="@+id/schoolTelephoneButton"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    app:srcCompat="@drawable/ic_all_phone"
-                    android:contentDescription="@string/school_telephone_button"
-                    android:background="?attr/selectableItemBackgroundBorderless"
-                    android:tint="?colorPrimary"
-                    android:padding="4dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginLeft="8dp"
                     android:layout_marginTop="8dp"
                     android:layout_marginRight="8dp"
-                    android:layout_marginLeft="8dp"
-                    android:layout_gravity="center_vertical" />
+                    android:background="?attr/selectableItemBackgroundBorderless"
+                    android:contentDescription="@string/school_telephone_button"
+                    android:padding="4dp"
+                    app:srcCompat="@drawable/ic_all_phone"
+                    app:tint="?colorPrimary" />
             </LinearLayout>
 
             <LinearLayout
diff --git a/app/src/main/res/layout/fragment_schoolandteachers.xml b/app/src/main/res/layout/fragment_schoolandteachers.xml
index f9fec1dd..ae8c6c20 100644
--- a/app/src/main/res/layout/fragment_schoolandteachers.xml
+++ b/app/src/main/res/layout/fragment_schoolandteachers.xml
@@ -5,7 +5,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <io.github.wulkanowy.ui.widgets.MaterialTabLayout
+    <com.google.android.material.tabs.TabLayout
         android:id="@+id/schoolandteachersTabLayout"
         android:layout_width="match_parent"
         android:layout_height="48dp"
@@ -20,7 +20,7 @@
         tools:ignore="UnusedAttribute"
         tools:visibility="visible" />
 
-    <androidx.viewpager.widget.ViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/schoolandteachersViewPager"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/app/src/main/res/layout/fragment_timetable.xml b/app/src/main/res/layout/fragment_timetable.xml
index bd37a766..d2ba8152 100644
--- a/app/src/main/res/layout/fragment_timetable.xml
+++ b/app/src/main/res/layout/fragment_timetable.xml
@@ -142,8 +142,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/timetableNavDate"
@@ -167,7 +167,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </FrameLayout>
diff --git a/app/src/main/res/layout/fragment_timetable_additional.xml b/app/src/main/res/layout/fragment_timetable_additional.xml
index 61eb4445..a71f7545 100644
--- a/app/src/main/res/layout/fragment_timetable_additional.xml
+++ b/app/src/main/res/layout/fragment_timetable_additional.xml
@@ -131,8 +131,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/additionalLessonsNavDate"
@@ -156,7 +156,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </FrameLayout>
diff --git a/app/src/main/res/layout/fragment_timetable_completed.xml b/app/src/main/res/layout/fragment_timetable_completed.xml
index 1a890fe1..e089275d 100644
--- a/app/src/main/res/layout/fragment_timetable_completed.xml
+++ b/app/src/main/res/layout/fragment_timetable_completed.xml
@@ -130,8 +130,8 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitStart"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_left" />
+            app:srcCompat="@drawable/ic_chevron_left"
+            app:tint="?colorPrimary" />
 
         <TextView
             android:id="@+id/completedLessonsNavDate"
@@ -155,7 +155,7 @@
             android:paddingRight="12dp"
             android:paddingBottom="8dp"
             android:scaleType="fitEnd"
-            android:tint="?colorPrimary"
-            app:srcCompat="@drawable/ic_chevron_right" />
+            app:srcCompat="@drawable/ic_chevron_right"
+            app:tint="?colorPrimary" />
     </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
 </FrameLayout>
diff --git a/app/src/main/res/layout/item_dashboard_admin_message.xml b/app/src/main/res/layout/item_dashboard_admin_message.xml
new file mode 100644
index 00000000..265cc14e
--- /dev/null
+++ b/app/src/main/res/layout/item_dashboard_admin_message.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="12dp"
+    android:layout_marginVertical="6dp"
+    app:cardElevation="4dp">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/dashboard_admin_message_item_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@+id/dashboard_admin_message_item_icon"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:src="@drawable/ic_error"
+            app:layout_constraintBottom_toBottomOf="@id/dashboard_admin_message_item_title"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@id/dashboard_admin_message_item_title"
+            tools:ignore="ContentDescription"
+            tools:tint="@android:color/black" />
+
+        <TextView
+            android:id="@+id/dashboard_admin_message_item_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
+            android:textStyle="bold"
+            android:textSize="18sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="@tools:sample/lorem" />
+
+        <TextView
+            android:id="@+id/dashboard_admin_message_item_description"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="16dp"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title"
+            app:lineHeight="20dp"
+            tools:maxLines="5"
+            tools:text="@tools:sample/lorem/random" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_dashboard_lessons.xml b/app/src/main/res/layout/item_dashboard_lessons.xml
index a2a92c54..9156c1a2 100644
--- a/app/src/main/res/layout/item_dashboard_lessons.xml
+++ b/app/src/main/res/layout/item_dashboard_lessons.xml
@@ -42,6 +42,19 @@
             app:layout_constraintBottom_toBottomOf="@id/dashboard_lessons_item_title"
             app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_title" />
 
+        <TextView
+            android:id="@+id/dashboard_lessons_item_title_today_and_tomorrow"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:text="@string/dashboard_timetable_title_today_and_tomorrow"
+            android:textColor="?colorOnSurface"
+            android:textSize="14sp"
+            app:layout_constraintBaseline_toBaselineOf="@id/dashboard_lessons_item_title"
+            app:layout_constraintBottom_toBottomOf="@id/dashboard_lessons_item_title"
+            app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_title"
+            tools:visibility="invisible" />
+
         <TextView
             android:id="@+id/dashboard_lessons_item_first_title"
             android:layout_width="wrap_content"
@@ -86,7 +99,7 @@
             android:paddingStart="6dp"
             android:paddingEnd="6dp"
             android:paddingBottom="1dp"
-            android:textColor="@android:color/white"
+            android:textColor="?colorOnPrimary"
             android:textSize="13sp"
             app:backgroundTint="?colorPrimary"
             app:layout_constraintEnd_toEndOf="parent"
@@ -224,8 +237,8 @@
             android:layout_marginTop="16dp"
             android:layout_marginEnd="16dp"
             android:layout_marginBottom="16dp"
-            android:gravity="center_vertical"
             android:drawablePadding="8dp"
+            android:gravity="center_vertical"
             android:text="@string/dashboard_timetable_error"
             android:textColor="?colorError"
             android:textSize="14sp"
diff --git a/app/src/main/res/layout/item_homework_dialog_details.xml b/app/src/main/res/layout/item_homework_dialog_details.xml
index abc371da..9d560ba5 100644
--- a/app/src/main/res/layout/item_homework_dialog_details.xml
+++ b/app/src/main/res/layout/item_homework_dialog_details.xml
@@ -10,26 +10,37 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal" >
+        android:orientation="horizontal">
 
         <TextView
             android:id="@+id/allDetailsHeader"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_weight="1"
             android:layout_marginStart="0dp"
             android:layout_marginTop="24dp"
             android:layout_marginEnd="24dp"
+            android:layout_weight="1"
             android:text="@string/all_details"
             android:textSize="21sp"
             android:textStyle="bold" />
 
+        <ImageButton
+            android:id="@+id/homework_dialog_delete"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:background="?selectableItemBackgroundBorderless"
+            android:padding="5dp"
+            app:srcCompat="@drawable/ic_menu_message_delete"
+            app:tint="?colorPrimary"
+            tools:ignore="ContentDescription" />
+
         <ImageButton
             android:id="@+id/homework_dialog_full_screen"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="?selectableItemBackgroundBorderless"
             android:layout_marginTop="24dp"
+            android:background="?selectableItemBackgroundBorderless"
             android:padding="5dp"
             app:srcCompat="@drawable/ic_fullscreen"
             app:tint="?colorOnBackground"
@@ -39,8 +50,8 @@
             android:id="@+id/homework_dialog_full_screen_exit"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="?selectableItemBackgroundBorderless"
             android:layout_marginTop="24dp"
+            android:background="?selectableItemBackgroundBorderless"
             android:padding="5dp"
             android:visibility="gone"
             app:srcCompat="@drawable/ic_fullscreen_exit"
@@ -153,13 +164,14 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="0dp"
         android:layout_marginEnd="24dp"
-        android:paddingStart="0dp"
-        android:paddingEnd="16dp"
+        android:layout_marginBottom="16dp"
         android:autoLink="web"
         android:lineSpacingMultiplier="1.2"
+        android:paddingStart="0dp"
+        android:paddingEnd="16dp"
         android:text="@string/all_no_data"
         android:textIsSelectable="true"
         android:textSize="16sp"
+        tools:maxLines="7"
         tools:text="@tools:sample/lorem/random" />
-
 </LinearLayout>
diff --git a/app/src/main/res/layout/item_notifications_center.xml b/app/src/main/res/layout/item_notifications_center.xml
index ae3f5586..16a7ae0c 100644
--- a/app/src/main/res/layout/item_notifications_center.xml
+++ b/app/src/main/res/layout/item_notifications_center.xml
@@ -33,6 +33,7 @@
             android:layout_marginStart="16dp"
             android:layout_marginTop="6dp"
             android:layout_marginEnd="16dp"
+            android:fontFamily="sans-serif-medium"
             android:textSize="15sp"
             app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
             app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/scrollable_header_about.xml b/app/src/main/res/layout/scrollable_header_about.xml
index e203d98d..5a7669fd 100644
--- a/app/src/main/res/layout/scrollable_header_about.xml
+++ b/app/src/main/res/layout/scrollable_header_about.xml
@@ -6,6 +6,7 @@
     android:layout_height="wrap_content"
     android:minHeight="104dp"
     android:orientation="vertical"
+    android:paddingHorizontal="20dp"
     tools:context=".ui.modules.about.AboutAdapter">
 
     <ImageView
@@ -23,12 +24,13 @@
 
     <TextView
         android:id="@+id/aboutScrollableHeaderName"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:text="@string/app_name"
         android:textSize="24sp"
         app:layout_constraintBottom_toBottomOf="@id/aboutScrollableHeaderIcon"
         app:layout_constraintLeft_toRightOf="@id/aboutScrollableHeaderIcon"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toTopOf="@id/aboutScrollableHeaderIcon" />
+        app:layout_constraintTop_toTopOf="@id/aboutScrollableHeaderIcon"
+        app:layout_constraintWidth_max="wrap" />
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/subitem_dashboard_conferences.xml b/app/src/main/res/layout/subitem_dashboard_conferences.xml
index e8080936..8da2e19b 100644
--- a/app/src/main/res/layout/subitem_dashboard_conferences.xml
+++ b/app/src/main/res/layout/subitem_dashboard_conferences.xml
@@ -2,7 +2,8 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <TextView
         android:id="@+id/dashboard_homework_subitem_title"
@@ -12,7 +13,7 @@
         android:layout_marginEnd="24dp"
         android:ellipsize="end"
         android:maxLines="1"
-        android:text="Spotaknie z rodzicami/opiekunami"
+        tools:text="Spotaknie z rodzicami/opiekunami"
         android:textSize="13sp"
         app:layout_constraintEnd_toStartOf="@id/dashboard_homework_subitem_time"
         app:layout_constraintStart_toStartOf="parent"
@@ -24,7 +25,7 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="6dp"
         android:layout_marginEnd="12dp"
-        android:text="17:00  02.11.2020"
+        tools:text="17:00  02.11.2020"
         android:textSize="13sp"
         android:textColor="?android:textColorSecondary"
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml
index 5e2f10fb..5252f79b 100644
--- a/app/src/main/res/values-cs/preferences_values.xml
+++ b/app/src/main/res/values-cs/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Barvy známek v deníku</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>Až 1 najednou</item>
+        <item>Vždy rozbalené</item>
+        <item>Neomezené rozbalené</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Průměr známek pouze z vybraného semestru</item>
         <item>Průměr z průměrů z obou semestrů</item>
@@ -48,7 +53,7 @@
     <string-array name="dashboard_tile_entries">
         <item>Šťastné číslo</item>
         <item>Nepřečtené zprávy</item>
-        <item>Docházka</item>
+        <item>Frekvence</item>
         <item>Lekce</item>
         <item>Známky</item>
         <item>Domácí úkoly</item>
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 1557ecc1..687962e4 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -4,7 +4,7 @@
     <string name="login_title">Přihlášení</string>
     <string name="main_title">Wulkanowy</string>
     <string name="grade_title">Známky</string>
-    <string name="attendance_title">Docházka</string>
+    <string name="attendance_title">Frekvence</string>
     <string name="exam_title">Zkoušky</string>
     <string name="timetable_title">Plán lekce</string>
     <string name="settings_title">Nastavení</string>
@@ -17,6 +17,7 @@
     <string name="license_title">Licence</string>
     <string name="message_title">Zprávy</string>
     <string name="send_message_title">Nová zpráva</string>
+    <string name="add_homework_title">Nový domácí úkol</string>
     <string name="note_title">Poznámky a úspěchy</string>
     <string name="homework_title">Domácí úkoly</string>
     <string name="account_title">Manažer účtů</string>
@@ -29,7 +30,7 @@
     <string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Přihlaste se pomocí studentského nebo rodičovského účtu</string>
-    <string name="login_header_symbol">Zadejte symbol ze stránky deníku</string>
+    <string name="login_header_symbol">Zadejte symbol ze stránky deníku: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Uživatelské jméno</string>
     <string name="login_email_hint">Email</string>
     <string name="login_login_pesel_email_hint">Přihlášení, číslo PESEL nebo e-mail</string>
@@ -53,15 +54,14 @@
     <string name="login_invalid_custom_email">Použijte přiřazené přihlašovací nebo e-mail v @%1$s</string>
     <string name="login_invalid_symbol">Neplatný symbol</string>
     <string name="login_incorrect_symbol">Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+</string>
-    <string name="login_field_required">Toto pole je povinné</string>
     <string name="login_duplicate_student">Vybraný žák je už přihlášen</string>
     <string name="login_symbol_helper">Symbol najdete na stránce deníku v &#160;<b>Uczeń</b> →&#160;<b>Dostęp Mobilny</b> →&#160;<b>Zarejestruj urządzenie mobilne</b>.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole <b>Variace deníku UONET+</b>. Wulkanowy v tuto chvíli nezjistí předškolní żaków</string>
     <string name="login_select_student">Vyberte žáky, kteří se mají do aplikace přihlásit</string>
     <string name="login_advanced">Jiné možnosti</string>
-    <string name="login_advanced_warning_mobile_api">V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení</string>
+    <string name="login_advanced_warning_mobile_api">V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí frekvencí, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení</string>
     <string name="login_advanced_warning_scraper">Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka</string>
     <string name="login_advanced_warning_hybrid">Kombinace nejlepších vlastností ostatních dvou režimů. Funguje rychleji než scraper a poskytuje funkce, které nejsou k dispozici v režimu Mobile API. Je to v experimentální fázi</string>
-    <string name="login_privacy_policy">Zásady ochrany osobních údajů</string>
+    <string name="login_privacy_policy">Ochrana osobních údajů</string>
     <string name="login_contact_header">Problémy s přihlášením? Napište nám!</string>
     <string name="login_contact_email">Email</string>
     <string name="login_contact_discord">Discord</string>
@@ -165,6 +165,34 @@
     <string name="timetable_now">Teď: %s</string>
     <string name="timetable_next">Za chvíli: %s</string>
     <string name="timetable_later">Později: %s</string>
+    <string name="timetable_notify_lesson">%1$s lekce %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Změna učebny z %1$s na %2$s</string>
+    <string name="timetable_notify_change_teacher">Změna učitele z %1$s na %2$s</string>
+    <string name="timetable_notify_change_subject">Změna předmětu z %1$s na %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Změna plánu lekcí</item>
+        <item quantity="few">Změny plánu lekcí</item>
+        <item quantity="many">Změny plánu lekcí</item>
+        <item quantity="other">Změny plánu lekcí</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d změna plánu lekcí</item>
+        <item quantity="few">%1$s - %2$d změny plánu lekcí</item>
+        <item quantity="many">%1$s - %2$d změn plánu lekcí</item>
+        <item quantity="other">%1$s - %2$d změn plánu lekcí</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d změna plánu lekcí</item>
+        <item quantity="few">%1$d změny plánu lekcí</item>
+        <item quantity="many">%1$d změn plánu lekcí</item>
+        <item quantity="other">%1$d změn plánu lekcí</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d změna</item>
+        <item quantity="few">%d změny</item>
+        <item quantity="many">%d změn</item>
+        <item quantity="other">%d změn</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Dokončené lekce</string>
     <string name="completed_lessons_button">Zobrazit dokončené lekce</string>
@@ -177,7 +205,7 @@
     <string name="additional_lessons_button">Zobrazit další lekce</string>
     <string name="additional_lessons_no_items">Žádné informace o dalších lekcích</string>
     <!--Attendance-->
-    <string name="attendance_summary_button">Shrnutí docházky</string>
+    <string name="attendance_summary_button">Shrnutí frekvencí</string>
     <string name="attendance_absence_school">Neprítomnosť zo školských dôvodov</string>
     <string name="attendance_absence_excused">Omluvená nepřítomnost</string>
     <string name="attendance_absence_unexcused">Neomluvená nepřítomnost</string>
@@ -194,6 +222,24 @@
     <string name="attendance_excuse_success">Žádost o omluvu nepřítomnosti byla úspěšně odeslána!</string>
     <string name="attendance_excuse_no_selection">Musíte vybrat alespoň jednu nepřítomnost!</string>
     <string name="attendance_excuse_title">Ospravedlnit</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">Nové frekvence</item>
+        <item quantity="few">Nové frekvence</item>
+        <item quantity="many">Nové frekvence</item>
+        <item quantity="other">Nové frekvence</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d nové frekvence</item>
+        <item quantity="few">%1$d nové frekvence</item>
+        <item quantity="many">%1$d nových frekvencí</item>
+        <item quantity="other">%1$d nových frekvencí</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d frekvence</item>
+        <item quantity="few">%d frekvence</item>
+        <item quantity="many">%d frekvencí</item>
+        <item quantity="other">%d frekvencí</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Společně</string>
     <!--Exam-->
@@ -328,6 +374,9 @@
     <string name="homework_no_items">Žádné informace o domácích úkolech</string>
     <string name="homework_mark_as_done">Označit jako hotové</string>
     <string name="homework_mark_as_undone">Neudělané</string>
+    <string name="homework_add">Přidat domácí úkol</string>
+    <string name="homework_add_success">Domácí úkol byl úspěšně přidán</string>
+    <string name="homework_delete_success">Domácí úkol byl úspěšně odstraněn</string>
     <string name="homework_attachments">Přílohy</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Nový domácí úkol</item>
@@ -450,11 +499,11 @@
     <string name="about_faq_summary">Přečtěte si často kladené otázky</string>
     <string name="about_discord">Server Discord</string>
     <string name="about_discord_summary">Připojte se ke komunitě Wulkanového</string>
-    <string name="about_facebook">Facebooková fanpage</string>
+    <string name="about_facebook">Stránka na Facebooku</string>
     <string name="about_twitter">Twitter stránka</string>
     <string name="about_twitter_summary">Sledujte nás na Twitteru</string>
-    <string name="about_facebook_summary">Stejně jako naše facebooková fanpage</string>
-    <string name="about_privacy">Zásady ochrany osobních údajů</string>
+    <string name="about_facebook_summary">Dejte like naší stránce na Facebooku</string>
+    <string name="about_privacy">Ochrana osobních údajů</string>
     <string name="about_privacy_summary">Pravidla pro shromažďování osobních údajů</string>
     <string name="about_system">Systemová nastavení</string>
     <string name="about_system_summary">Otevřít systémová nastavení</string>
@@ -499,6 +548,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Lekce</string>
     <string name="dashboard_timetable_title_tomorrow">(Zítra)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Dnes a zítra)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Za chvíli:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string>
     <string name="dashboard_timetable_first_lesson_title_first">První:</string>
@@ -594,6 +644,10 @@
     <string name="all_no">Ne</string>
     <string name="all_save">Uložit</string>
     <string name="all_title">Titul</string>
+    <string name="all_add">Přidat</string>
+    <string name="all_copied">Zkopírováno</string>
+    <string name="all_undo">Vrátit</string>
+    <string name="all_change">Změnit</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Žádné lekce</string>
     <string name="widget_timetable_theme_title">Vybrat motiv</string>
@@ -601,13 +655,13 @@
     <string name="widget_timetable_theme_dark">Tmavý</string>
     <string name="widget_timetable_theme_system">Motiv systému</string>
     <!--Preferences-->
-    <string name="pref_view_header">Vzhled a chování aplikací</string>
+    <string name="pref_view_header">Aplikace</string>
     <string name="pref_view_list">Výchozí zobrazení</string>
     <string name="pref_view_grade_average_mode">Možnosti vypočítaného průměru</string>
     <string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
     <string name="pref_view_present">Zobrazit přítomnost</string>
     <string name="pref_view_app_theme">Motiv</string>
-    <string name="pref_view_expand_grade">Rozbalit známky</string>
+    <string name="pref_view_expand_grade">Rozvíjení známek</string>
     <string name="pref_view_timetable_show_timers">Označit aktuální lekci</string>
     <string name="pref_view_timetable_show_groups">Zobrazit skupiny vedle předmětů</string>
     <string name="pref_view_grade_statistics_list">Zobrazit seznam grafů v známkách třídy</string>
@@ -616,6 +670,7 @@
     <string name="pref_view_grade_sorting_mode">Třídění předmětů</string>
     <string name="pref_view_app_language">Jazyk</string>
     <string name="pref_notify_header">Upozornění</string>
+    <string name="pref_notify_header_other">Jiné</string>
     <string name="pref_notify_switch">Zobrazit upozornění</string>
     <string name="pref_notify_upcoming_lessons_switch">Zobrazit upozornění o nadcházející lekci</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Nastavit upozornění o nadcházející lekci jako trvalé</string>
@@ -644,16 +699,25 @@
     <string name="pref_other_grade_modifier_minus">Hodnota mínusu</string>
     <string name="pref_other_fill_message_content">Odpovědět s historií zpráv</string>
     <string name="pref_other_optional_arithmetic_average">Vypočítat aritmetický průměr, pokud žádná známka nemá váhu</string>
+    <string name="pref_ads_support_category_name">Podpora</string>
+    <string name="pref_ads_support">Podívejte se na jednu reklamu pro podporu projektu</string>
+    <string name="pref_ads_privacy_title">Souhlas se zpracováním dat</string>
+    <string name="pref_ads_privacy_description">Jestli chcete sledovat reklamu, musíte souhlasit s podmínkami zpracování údajů v našich Zásadách Ochrany Osobních Údajů</string>
+    <string name="pref_ads_privacy_agree">Souhlasím</string>
+    <string name="pref_ads_privacy_link">Ochrana osobních údajů</string>
+    <string name="pref_ads_loading">Reklama se načítá</string>
     <string name="pref_settings_advanced_title">Pokročilé</string>
     <string name="pref_settings_appearance_title">Vzhled a chování</string>
     <string name="pref_settings_notifications_title">Upozornění</string>
     <string name="pref_settings_sync_title">Synchronizace</string>
+    <string name="pref_settings_ads_title">Reklamy</string>
     <string name="pref_grades_appearance_header">Známky</string>
     <string name="pref_dashboard_appearance_header">Domů</string>
     <string name="pref_dashboard_appearance_tiles_title">Viditelnost dlaždic</string>
-    <string name="pref_attendance_appearance_view">Docházka</string>
+    <string name="pref_attendance_appearance_view">Frekvence</string>
     <string name="pref_timetable_appearance_view">Plán lekce</string>
     <string name="pref_grades_advanced_header">Známky</string>
+    <string name="pref_counted_average_advanced_header">Vypočítaný průměr</string>
     <string name="pref_messages_advanced_header">Zprávy</string>
     <string name="pref_appearance_category">Vzhled a chování</string>
     <string name="pref_appearance_category_summary">Jazyky, motivy, třídění předmětů</string>
@@ -663,7 +727,8 @@
     <string name="pref_sync_category_summary">Automatická aktualizace, interval aktualizací</string>
     <string name="pref_advanced_category_summary">Hodnota plusu a mínusu, výpočet průměru</string>
     <string name="pref_advanced_category">Pokročilé</string>
-    <string name="pref_about_category_summary">Verze aplikace, tvůrci, sociální portály, licence</string>
+    <string name="pref_about_category_summary">Verze aplikace, tvůrci, sociální portály</string>
+    <string name="pref_ads_category_summary">Zobrazování reklam, podpora projektu</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Nové známky</string>
     <string name="channel_new_homework">Nové domácí úkoly</string>
@@ -676,6 +741,8 @@
     <string name="channel_push">Push upozornění</string>
     <string name="channel_upcoming_lessons">Nadcházející lekce</string>
     <string name="channel_debug">Ladění</string>
+    <string name="channel_change_timetable">Změny plánu lekcí</string>
+    <string name="channel_new_attendance">Nové frekvence</string>
     <!--Colors-->
     <string name="all_black">Černá</string>
     <string name="all_red">Červená</string>
@@ -683,10 +750,6 @@
     <string name="all_green">Zelená</string>
     <string name="all_purple">Fialová</string>
     <string name="all_empty_color">Žádná barva</string>
-    <!--Others-->
-    <string name="all_copied">Zkopírováno</string>
-    <string name="all_undo">Vrátit</string>
-    <string name="all_change">Změnit</string>
     <!--Update helper-->
     <string name="update_download_started">Stahování aktualizací začalo…</string>
     <string name="update_download_success">Aktualizace byla stažena.</string>
@@ -703,4 +766,5 @@
     <string name="error_unknown">Vyskytla se neočekávaná chyba</string>
     <string name="error_feature_disabled">Funkce je deaktivována přes vaší školou</string>
     <string name="error_feature_not_available">Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API</string>
+    <string name="error_field_required">Toto pole je povinné</string>
 </resources>
diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml
index 699ca824..23e54cb7 100644
--- a/app/src/main/res/values-de/preferences_values.xml
+++ b/app/src/main/res/values-de/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Farben der Bewertungen im Logbuch</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>Up to 1 at once</item>
+        <item>Always expanded</item>
+        <item>Unlimited expansions</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Durchschnitt der Noten aus beiden Semestern</item>
         <item>Durchschnittswert der Durchschnittswerte beider Semester</item>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index de73e782..9ad6e372 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -6,7 +6,7 @@
     <string name="grade_title">Noten</string>
     <string name="attendance_title">Schulbesuch</string>
     <string name="exam_title">Prüfungen</string>
-    <string name="timetable_title">Zeitplan</string>
+    <string name="timetable_title">Stundenplan</string>
     <string name="settings_title">Einstellungen</string>
     <string name="more_title">Mehr</string>
     <string name="about_title">Über die Applikation</string>
@@ -17,6 +17,7 @@
     <string name="license_title">Lizenzen</string>
     <string name="message_title">Nachrichten</string>
     <string name="send_message_title">neue Nachricht</string>
+    <string name="add_homework_title">New homework</string>
     <string name="note_title">Eintragen und Erfolgen</string>
     <string name="homework_title">Hausaufgaben</string>
     <string name="account_title">Konten-Manager</string>
@@ -29,7 +30,7 @@
     <string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Melden Sie sich mit dem Studenten- oder Elternkonto an</string>
-    <string name="login_header_symbol">Geben Sie das Symbol von der Registerseite ein</string>
+    <string name="login_header_symbol">Geben Sie das Symbol von der Registerseite ein: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Benutzername</string>
     <string name="login_email_hint">Email</string>
     <string name="login_login_pesel_email_hint">Anmeldung, PESEL oder e-mail</string>
@@ -53,7 +54,6 @@
     <string name="login_invalid_custom_email">Benutze den zugewiesenen Login oder E-Mail in @%1$s</string>
     <string name="login_invalid_symbol">Ungültige symbol</string>
     <string name="login_incorrect_symbol">Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers</string>
-    <string name="login_field_required">Dieses Datenfeld ist erforderlich</string>
     <string name="login_duplicate_student">Ausgewählter Student ist bereits angemeldet.</string>
     <string name="login_symbol_helper">Das Symbol kann auf der Registerseite in&#160;<b>Uczeń</b>→&#160;<b>Dostęp Mobilny</b> →&#160;<b>Zarejestruj urządzenie mobilne</b>gefunden werden.\n\nStellen Sie sicher, dass Sie die entsprechende Registervariante im Feld <b>UONET+ Registervariante</b> auf dem vorherigen Bildschirm festgelegt haben. Wulkanowy erkennt zur Zeit keine Vorschulstudenten</string>
     <string name="login_select_student">Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen.</string>
@@ -151,6 +151,26 @@
     <string name="timetable_now">Jetzt: %s</string>
     <string name="timetable_next">In einem Moment: %s</string>
     <string name="timetable_later">Später: %s</string>
+    <string name="timetable_notify_lesson">%1$s lesson %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Change of room from %1$s to %2$s</string>
+    <string name="timetable_notify_change_teacher">Change of teacher from %1$s to %2$s</string>
+    <string name="timetable_notify_change_subject">Change of subject from %1$s to %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Timetable change</item>
+        <item quantity="other">Timetable changes</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d change in timetable</item>
+        <item quantity="other">%1$s - %2$d  changes in timetable</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d change in timetable</item>
+        <item quantity="other">%1$d changes in timetable</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d change</item>
+        <item quantity="other">%d changes</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Beendete Lektionen</string>
     <string name="completed_lessons_button">Beendete Lektionen anzeigen</string>
@@ -180,6 +200,18 @@
     <string name="attendance_excuse_success">Abwesenheitsentschuldigungsanfrage erfolgreich gesendet!</string>
     <string name="attendance_excuse_no_selection">Sie müssen mindestens eine Abwesenheit auswählen!</string>
     <string name="attendance_excuse_title">Verzeihung</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">New attendance</item>
+        <item quantity="other">New attendance</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d new attendance</item>
+        <item quantity="other">%1$d attendance</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d attendance</item>
+        <item quantity="other">%d attendance</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Gesamt</string>
     <!--Exam-->
@@ -284,6 +316,9 @@
     <string name="homework_no_items">Keine Informationen über Hausaufgaben</string>
     <string name="homework_mark_as_done">Gemacht</string>
     <string name="homework_mark_as_undone">Unvollständig</string>
+    <string name="homework_add">Add homework</string>
+    <string name="homework_add_success">Homework added successfully</string>
+    <string name="homework_delete_success">Homework deleted successfully</string>
     <string name="homework_attachments">Anhänge</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Neue hausaufgaben</item>
@@ -437,6 +472,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Lektionen</string>
     <string name="dashboard_timetable_title_tomorrow">(Morgen)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Today and tomorrow)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Gleich:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Bald:</string>
     <string name="dashboard_timetable_first_lesson_title_first">Erstens:</string>
@@ -518,6 +554,10 @@
     <string name="all_no">Nein</string>
     <string name="all_save">Speichern</string>
     <string name="all_title">Titel</string>
+    <string name="all_add">Add</string>
+    <string name="all_copied">Kopiert</string>
+    <string name="all_undo">lösen</string>
+    <string name="all_change">Ändern</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Keine Lektionen</string>
     <string name="widget_timetable_theme_title">Thema wählen</string>
@@ -525,13 +565,13 @@
     <string name="widget_timetable_theme_dark">Dunkel</string>
     <string name="widget_timetable_theme_system">Systemthema</string>
     <!--Preferences-->
-    <string name="pref_view_header">Aussehen &amp; Verhalten</string>
+    <string name="pref_view_header">App</string>
     <string name="pref_view_list">Standard Ansicht</string>
     <string name="pref_view_grade_average_mode">Berechnete Durchschnittsoptionen</string>
     <string name="pref_view_grade_average_force_calc">Mittelwertberechnung durch App erzwingen</string>
     <string name="pref_view_present">Anwesendheit zeigen</string>
     <string name="pref_view_app_theme">Thema</string>
-    <string name="pref_view_expand_grade">Noten erweitern</string>
+    <string name="pref_view_expand_grade">Grades expanding</string>
     <string name="pref_view_timetable_show_timers">Aktuelle Lektion markieren</string>
     <string name="pref_view_timetable_show_groups">Gruppen neben Schulfächen anzeigen</string>
     <string name="pref_view_grade_statistics_list">Liste der Diagramme in Klassenbewertungen anzeigen</string>
@@ -540,6 +580,7 @@
     <string name="pref_view_grade_sorting_mode">Schulfachen sortieren</string>
     <string name="pref_view_app_language">Sprache</string>
     <string name="pref_notify_header">Benachrichtigungen</string>
+    <string name="pref_notify_header_other">Other</string>
     <string name="pref_notify_switch">Benachrichtigungen anzeigen</string>
     <string name="pref_notify_upcoming_lessons_switch">Benachrichtigungen über bevorstehende Lektionen anzeigen</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Festlegen einer Benachrichtigung über die bevorstehende Lektion dauerhaft</string>
@@ -568,16 +609,25 @@
     <string name="pref_other_grade_modifier_minus">Wert des Minus</string>
     <string name="pref_other_fill_message_content">Antwort mit Nachrichtenhistorie</string>
     <string name="pref_other_optional_arithmetic_average">Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind</string>
+    <string name="pref_ads_support_category_name">Support</string>
+    <string name="pref_ads_support">Watch single ad to support project</string>
+    <string name="pref_ads_privacy_title">Consent to data processing</string>
+    <string name="pref_ads_privacy_description">To view an advertisement you must agree to the data processing terms of our Privacy Policy</string>
+    <string name="pref_ads_privacy_agree">Agree</string>
+    <string name="pref_ads_privacy_link">Privacy policy</string>
+    <string name="pref_ads_loading">Ad is loading</string>
     <string name="pref_settings_advanced_title">Erweitert</string>
     <string name="pref_settings_appearance_title">Aussehen &amp; Verhalten</string>
     <string name="pref_settings_notifications_title">Benachrichtigungen</string>
     <string name="pref_settings_sync_title">Synchronisierung</string>
+    <string name="pref_settings_ads_title">Advertisements</string>
     <string name="pref_grades_appearance_header">Noten</string>
     <string name="pref_dashboard_appearance_header">Dashboard</string>
     <string name="pref_dashboard_appearance_tiles_title">Sichtbarkeit der Kacheln</string>
     <string name="pref_attendance_appearance_view">Schulbesuch</string>
-    <string name="pref_timetable_appearance_view">Zeitplan</string>
+    <string name="pref_timetable_appearance_view">Stundenplan</string>
     <string name="pref_grades_advanced_header">Noten</string>
+    <string name="pref_counted_average_advanced_header">Calculated average</string>
     <string name="pref_messages_advanced_header">Nachrichten</string>
     <string name="pref_appearance_category">Aussehen &amp; Verhalten</string>
     <string name="pref_appearance_category_summary">Sprachen, Themen, Schulfachen sortieren</string>
@@ -587,7 +637,8 @@
     <string name="pref_sync_category_summary">Automatisches Update, Synchronisierungsintervall</string>
     <string name="pref_advanced_category_summary">Plus und Minus Werte, Durchschnittsberechnung</string>
     <string name="pref_advanced_category">Erweitert</string>
-    <string name="pref_about_category_summary">App-Version, Mitarbeiter, soziale Portale, Lizenzen</string>
+    <string name="pref_about_category_summary">App version, contributors, social portals</string>
+    <string name="pref_ads_category_summary">Displaying advertisements, project support</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Neue Noten</string>
     <string name="channel_new_homework">Neue Hausaufgaben</string>
@@ -600,6 +651,8 @@
     <string name="channel_push">Push-Benachrichtigungen</string>
     <string name="channel_upcoming_lessons">Bevorstehende Lektionen</string>
     <string name="channel_debug">Debuggen</string>
+    <string name="channel_change_timetable">Timetable change</string>
+    <string name="channel_new_attendance">New attendance</string>
     <!--Colors-->
     <string name="all_black">Schwarz</string>
     <string name="all_red">Rot</string>
@@ -607,10 +660,6 @@
     <string name="all_green">Grün</string>
     <string name="all_purple">Violett</string>
     <string name="all_empty_color">Keine Farbe</string>
-    <!--Others-->
-    <string name="all_copied">Kopiert</string>
-    <string name="all_undo">lösen</string>
-    <string name="all_change">Ändern</string>
     <!--Update helper-->
     <string name="update_download_started">Download der Updates wurde gestartet…</string>
     <string name="update_download_success">Ein Update wurde gerade heruntergeladen.</string>
@@ -627,4 +676,5 @@
     <string name="error_unknown">Ein unerwarteter Fehler ist aufgetreten</string>
     <string name="error_feature_disabled">Funktion, die von Ihrer Schule deaktiviert wurde</string>
     <string name="error_feature_not_available">Feature in diesem Modus nicht verfügbar</string>
+    <string name="error_field_required">This field is required</string>
 </resources>
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 5eca4680..881d5bd4 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -8,6 +8,7 @@
         <item name="colorError">@color/colorErrorLight</item>
         <item name="colorDivider">@color/colorDividerInverse</item>
         <item name="colorSwipeRefresh">@color/colorSwipeRefreshDark</item>
+        <item name="colorMessageMedium">@color/dashboard_message_medium_light</item>
         <item name="android:windowBackground">?colorSurface</item>
         <item name="android:textColor">?android:textColorPrimary</item>
         <item name="android:navigationBarColor">@color/colorNavigationBarLight</item>
diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml
index 5b0a90c6..c823e960 100644
--- a/app/src/main/res/values-pl/preferences_values.xml
+++ b/app/src/main/res/values-pl/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Kolory ocen w dzienniku</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>Do 1 na raz</item>
+        <item>Zawsze rozwinięte</item>
+        <item>Nieograniczone rozwijanie</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Średnia ocen tylko z wybranego semestru</item>
         <item>Średnia ze średnich z obu semestrów</item>
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 0a231606..4a1337e0 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -17,6 +17,7 @@
     <string name="license_title">Licencje</string>
     <string name="message_title">Wiadomości</string>
     <string name="send_message_title">Nowa wiadomość</string>
+    <string name="add_homework_title">Nowe zadanie domowe</string>
     <string name="note_title">Uwagi i osiągnięcia</string>
     <string name="homework_title">Zadania domowe</string>
     <string name="account_title">Menadżer kont</string>
@@ -29,10 +30,10 @@
     <string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Zaloguj się za pomocą konta ucznia lub rodzica</string>
-    <string name="login_header_symbol">Podaj symbol ze strony dziennika</string>
+    <string name="login_header_symbol">Podaj symbol ze strony dziennika dla konta: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Nazwa użytkownika</string>
-    <string name="login_email_hint">Email</string>
-    <string name="login_login_pesel_email_hint">Login, PESEL lub e-mail</string>
+    <string name="login_email_hint">Adres e-mail</string>
+    <string name="login_login_pesel_email_hint">Login, PESEL lub adres e-mail</string>
     <string name="login_password_hint">Hasło</string>
     <string name="login_host_hint">Odmiana dziennika UONET+</string>
     <string name="login_type_api">Mobilne API</string>
@@ -48,12 +49,11 @@
     <string name="login_invalid_pin">Nieprawidłowy PIN</string>
     <string name="login_invalid_token">Nieprawidłowy token</string>
     <string name="login_expired_token">Token stracił ważność</string>
-    <string name="login_invalid_email">Niepoprawny adres email</string>
-    <string name="login_invalid_login">Użyj przydzielonego loginu zamiast emaila</string>
-    <string name="login_invalid_custom_email">Użyj przypisanego loginu lub adresu e-mail w @%1$s</string>
-    <string name="login_invalid_symbol">Niepoprawny symbol</string>
+    <string name="login_invalid_email">Nieprawidłowy adres e-mail</string>
+    <string name="login_invalid_login">Użyj loginu zamiast adresu e-mail</string>
+    <string name="login_invalid_custom_email">Użyj loginu lub adresu e-mail w @%1$s</string>
+    <string name="login_invalid_symbol">Nieprawidłowy symbol</string>
     <string name="login_incorrect_symbol">Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+</string>
-    <string name="login_field_required">To pole jest wymagane</string>
     <string name="login_duplicate_student">Wybrany uczeń jest już zalogowany</string>
     <string name="login_symbol_helper">Symbol znajdziesz na stronie dziennika w&#160;<b>Uczeń</b> →&#160;<b>Dostęp Mobilny</b> →&#160;<b>Zarejestruj urządzenie mobilne</b>.\n\nUpewnij się, że w polu <b>Dziennik UONET+</b> na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika.\n\n<b>Wulkanowy na chwilę obecną nie wykrywa uczniów przedszkolnych (z zerówki)</b></string>
     <string name="login_select_student">Wybierz uczniów do zalogowania w aplikacji</string>
@@ -63,9 +63,9 @@
     <string name="login_advanced_warning_hybrid">Połączenie najlepszych cech dwóch pozostałych trybów. Działa szybciej niż scraper i zapewnia funkcje niedostępne w trybie Mobilne API. Jest w fazie eksperymentalnej</string>
     <string name="login_privacy_policy">Polityka prywatności</string>
     <string name="login_contact_header">Problemy z logowaniem? Napisz do nas!</string>
-    <string name="login_contact_email">Email</string>
+    <string name="login_contact_email">E-mail</string>
     <string name="login_contact_discord">Discord</string>
-    <string name="login_email_intent_title">Wyślij email</string>
+    <string name="login_email_intent_title">Wyślij wiadomość e-mail</string>
     <string name="login_recover_warning">Upewnij się, że została wybrana odpowiednia odmiana dziennika UONET+!</string>
     <string name="login_recover_button">Nie pamiętam hasła</string>
     <string name="login_recover_title">Przywróć swoje konto</string>
@@ -165,6 +165,34 @@
     <string name="timetable_now">Teraz: %s</string>
     <string name="timetable_next">Za chwilę: %s</string>
     <string name="timetable_later">Później: %s</string>
+    <string name="timetable_notify_lesson">%1$s lekcja %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Zmiana sali z %1$s na %2$s</string>
+    <string name="timetable_notify_change_teacher">Zmiana nauczyciela z %1$s na %2$s</string>
+    <string name="timetable_notify_change_subject">Zmiana przedmiotu z %1$s na %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Zmiana planu lekcji</item>
+        <item quantity="few">Zmiany planu lekcji</item>
+        <item quantity="many">Zmiany planu lekcji</item>
+        <item quantity="other">Zmiany planu lekcji</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d zmiana planu lekcji</item>
+        <item quantity="few">%1$s - %2$d zmiany planu lekcji</item>
+        <item quantity="many">%1$s - %2$d zmian planu lekcji</item>
+        <item quantity="other">%1$s - %2$d zmian planu lekcji</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d zmiana planu lekcji</item>
+        <item quantity="few">%1$d zmiany planu lekcji</item>
+        <item quantity="many">%1$d zmian planu lekcji</item>
+        <item quantity="other">%1$d zmian planu lekcji</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d zmiana</item>
+        <item quantity="few">%d zmiany</item>
+        <item quantity="many">%d zmian</item>
+        <item quantity="other">%d zmian</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Lekcje zrealizowane</string>
     <string name="completed_lessons_button">Zobacz lekcje zrealizowane</string>
@@ -194,6 +222,24 @@
     <string name="attendance_excuse_success">Prośba o usprawiedliwienie została pomyślnie wysłana!</string>
     <string name="attendance_excuse_no_selection">Musisz wybrać co najmniej jedną nieobecność!</string>
     <string name="attendance_excuse_title">Usprawiedliw</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">Nowa frekwencja</item>
+        <item quantity="few">Nowe frekwencje</item>
+        <item quantity="many">Nowe frekwencje</item>
+        <item quantity="other">Nowe frekwencje</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d nowa frekwencja</item>
+        <item quantity="few">%1$d nowe frekwencje</item>
+        <item quantity="many">%1$d nowych frekwencji</item>
+        <item quantity="other">%1$d nowych frekwencji</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d frekwencja</item>
+        <item quantity="few">%d frekwencje</item>
+        <item quantity="many">%d frekwencji</item>
+        <item quantity="other">%d frekwencji</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Razem</string>
     <!--Exam-->
@@ -328,6 +374,9 @@
     <string name="homework_no_items">Brak zadań domowych</string>
     <string name="homework_mark_as_done">Wykonane</string>
     <string name="homework_mark_as_undone">Niewykonane</string>
+    <string name="homework_add">Dodaj zadanie domowe</string>
+    <string name="homework_add_success">Zadanie domowe pomyślnie dodane</string>
+    <string name="homework_delete_success">Zadanie domowe pomyślnie usunięte</string>
     <string name="homework_attachments">Załączniki</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Nowe zadanie domowe</item>
@@ -477,7 +526,7 @@
     <string name="student_info_parents_name">Imiona matki i ojca</string>
     <string name="student_info_phone">Telefon</string>
     <string name="student_info_cellphone">Telefon komórkowy</string>
-    <string name="student_info_email">E-mail</string>
+    <string name="student_info_email">Adres e-mail</string>
     <string name="student_info_address">Adres zamieszkania</string>
     <string name="student_info_registered_address">Adres zameldowania</string>
     <string name="student_info_correspondence_address">Adres korespondencyjny</string>
@@ -499,6 +548,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Lekcje</string>
     <string name="dashboard_timetable_title_tomorrow">(Jutro)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Dzisiaj i jutro)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Za chwilę:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string>
     <string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string>
@@ -594,6 +644,10 @@
     <string name="all_no">Nie</string>
     <string name="all_save">Zapisz</string>
     <string name="all_title">Tytuł</string>
+    <string name="all_add">Dodaj</string>
+    <string name="all_copied">Skopiowano</string>
+    <string name="all_undo">Cofnij</string>
+    <string name="all_change">Zmień</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Brak lekcji</string>
     <string name="widget_timetable_theme_title">Wybierz motyw</string>
@@ -601,13 +655,13 @@
     <string name="widget_timetable_theme_dark">Ciemny</string>
     <string name="widget_timetable_theme_system">Motyw systemu</string>
     <!--Preferences-->
-    <string name="pref_view_header">Wygląd i zachowanie aplikacji</string>
+    <string name="pref_view_header">Aplikacja</string>
     <string name="pref_view_list">Domyślny widok</string>
     <string name="pref_view_grade_average_mode">Opcje obliczonej średniej</string>
     <string name="pref_view_grade_average_force_calc">Wymuś obliczanie średniej przez aplikację</string>
     <string name="pref_view_present">Pokazuj obecność</string>
     <string name="pref_view_app_theme">Motyw</string>
-    <string name="pref_view_expand_grade">Rozwiń oceny</string>
+    <string name="pref_view_expand_grade">Rozwijanie ocen</string>
     <string name="pref_view_timetable_show_timers">Oznaczaj bieżącą lekcję</string>
     <string name="pref_view_timetable_show_groups">Pokazuj grupę obok przedmiotu</string>
     <string name="pref_view_grade_statistics_list">Pokazuj listę wykresów w ocenach klasy</string>
@@ -616,6 +670,7 @@
     <string name="pref_view_grade_sorting_mode">Sortowanie przedmiotów</string>
     <string name="pref_view_app_language">Język</string>
     <string name="pref_notify_header">Powiadomienia</string>
+    <string name="pref_notify_header_other">Inne</string>
     <string name="pref_notify_switch">Pokazuj powiadomienia</string>
     <string name="pref_notify_upcoming_lessons_switch">Pokazuj powiadomienia o nadchodzących lekcjach</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Ustaw powiadomienie o nadchodzącej lekcji jako trwałe</string>
@@ -644,16 +699,25 @@
     <string name="pref_other_grade_modifier_minus">Wartość minusa</string>
     <string name="pref_other_fill_message_content">Odpowiadaj z historią wiadomości</string>
     <string name="pref_other_optional_arithmetic_average">Licz średnią arytmetyczną, gdy żadna ocena nie ma wagi</string>
+    <string name="pref_ads_support_category_name">Wsparcie</string>
+    <string name="pref_ads_support">Obejrzyj pojedynczą reklamę, aby wesprzeć projekt</string>
+    <string name="pref_ads_privacy_title">Zgoda na przetwarzanie danych</string>
+    <string name="pref_ads_privacy_description">Aby obejrzeć reklamę, musisz zaakceptować warunki przetwarzania danych zawarte w naszej Polityce Prywatności</string>
+    <string name="pref_ads_privacy_agree">Akceptuję</string>
+    <string name="pref_ads_privacy_link">Polityka prywatności</string>
+    <string name="pref_ads_loading">Ładowanie reklamy</string>
     <string name="pref_settings_advanced_title">Zaawansowane</string>
     <string name="pref_settings_appearance_title">Wygląd i zachowanie</string>
     <string name="pref_settings_notifications_title">Powiadomienia</string>
     <string name="pref_settings_sync_title">Synchronizacja</string>
+    <string name="pref_settings_ads_title">Reklamy</string>
     <string name="pref_grades_appearance_header">Oceny</string>
     <string name="pref_dashboard_appearance_header">Start</string>
     <string name="pref_dashboard_appearance_tiles_title">Widoczność kafelków</string>
     <string name="pref_attendance_appearance_view">Frekwencja</string>
     <string name="pref_timetable_appearance_view">Plan lekcji</string>
     <string name="pref_grades_advanced_header">Oceny</string>
+    <string name="pref_counted_average_advanced_header">Obliczona średnia</string>
     <string name="pref_messages_advanced_header">Wiadomości</string>
     <string name="pref_appearance_category">Wygląd i zachowanie</string>
     <string name="pref_appearance_category_summary">Języki, motywy, sortowanie przedmiotów</string>
@@ -663,7 +727,8 @@
     <string name="pref_sync_category_summary">Automatyczna aktualizacja, interwał synchronizacji</string>
     <string name="pref_advanced_category_summary">Wartości plusa i minusa, obliczanie średniej</string>
     <string name="pref_advanced_category">Zaawansowane</string>
-    <string name="pref_about_category_summary">Wersja aplikacji, twórcy, media społecznościowe, licencje</string>
+    <string name="pref_about_category_summary">Wersja aplikacji, twórcy, media społecznościowe</string>
+    <string name="pref_ads_category_summary">Wyświetlanie reklam, wsparcie projektu</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Nowe oceny</string>
     <string name="channel_new_homework">Nowe zadania domowe</string>
@@ -676,6 +741,8 @@
     <string name="channel_push">Powiadomienia push</string>
     <string name="channel_upcoming_lessons">Nadchodzące lekcje</string>
     <string name="channel_debug">Debugowanie</string>
+    <string name="channel_change_timetable">Zmiany planu lekcji</string>
+    <string name="channel_new_attendance">Nowe frekwencje</string>
     <!--Colors-->
     <string name="all_black">Czarny</string>
     <string name="all_red">Czerwony</string>
@@ -683,10 +750,6 @@
     <string name="all_green">Zielony</string>
     <string name="all_purple">Fioletowy</string>
     <string name="all_empty_color">Brak koloru</string>
-    <!--Others-->
-    <string name="all_copied">Skopiowano</string>
-    <string name="all_undo">Cofnij</string>
-    <string name="all_change">Zmień</string>
     <!--Update helper-->
     <string name="update_download_started">Rozpoczęto pobieranie aktualizacji…</string>
     <string name="update_download_success">Aktualizacja została pobrana.</string>
@@ -703,4 +766,5 @@
     <string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
     <string name="error_feature_disabled">Funkcja wyłączona przez szkołę</string>
     <string name="error_feature_not_available">Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API</string>
+    <string name="error_field_required">To pole jest wymagane</string>
 </resources>
diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml
index 4920068e..cfdaa957 100644
--- a/app/src/main/res/values-ru/preferences_values.xml
+++ b/app/src/main/res/values-ru/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Цвета оценок в дневнике</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>До 1 за раз</item>
+        <item>Всегда развернуто</item>
+        <item>Неограниченные расширения</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Средние оценки только с выбранного семестра</item>
         <item>Средние значения для обоих семестров</item>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 92af176f..c5dc73e3 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -17,6 +17,7 @@
     <string name="license_title">Лицензии</string>
     <string name="message_title">Сообщения</string>
     <string name="send_message_title">Новое сообщение</string>
+    <string name="add_homework_title">Новая домашняя работа</string>
     <string name="note_title">Предупреждения и свершения</string>
     <string name="homework_title">Домашние задания</string>
     <string name="account_title">Менеджер аккаунтов</string>
@@ -29,7 +30,7 @@
     <string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Авторизируйтесь при помощи аккаунта ученика или родителя</string>
-    <string name="login_header_symbol">Введите символ со страницы регистрации</string>
+    <string name="login_header_symbol">Введите символ со страницы регистрации: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Имя пользователя</string>
     <string name="login_email_hint">Электронная почта</string>
     <string name="login_login_pesel_email_hint">Логин, PESEL или электронная почта</string>
@@ -53,7 +54,6 @@
     <string name="login_invalid_custom_email">Использовать назначенный логин или email в @%1$s</string>
     <string name="login_invalid_symbol">Неправильный символ</string>
     <string name="login_incorrect_symbol">Студент не найден. Подтвердите символ и выбранный вариант регистра UONET+</string>
-    <string name="login_field_required">Это обязательное поле</string>
     <string name="login_duplicate_student">Данный ученик уже авторизован</string>
     <string name="login_symbol_helper">Этот символ можно найти на странице регистрации в &#160;<b>Ученик</b> → &#160;<b>Телефонный доступ</b> → &#160;<b> Зарегистрируйте мобильное устройство</b>.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле <b>Разновидностью бревна UONET+</b> на предыдущем экране. Вулкановый на данный момент не обнаруживает дошкольников</string>
     <string name="login_select_student">Выберите учеников для авторизации в приложении</string>
@@ -165,6 +165,34 @@
     <string name="timetable_now">Сейчас: %s</string>
     <string name="timetable_next">Следующий: %s</string>
     <string name="timetable_later">Позже: %s</string>
+    <string name="timetable_notify_lesson">%1$s урок %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Изменить комнату с %1$s на %2$s</string>
+    <string name="timetable_notify_change_teacher">Изменить учителя с %1$s на %2$s</string>
+    <string name="timetable_notify_change_subject">Изменить тему с %1$s на %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Изменение расписания</item>
+        <item quantity="few">Изменение расписания</item>
+        <item quantity="many">Изменение расписания</item>
+        <item quantity="other">Изменение расписания</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d изменений в расписании</item>
+        <item quantity="few">%1$s - %2$d изменений в расписании</item>
+        <item quantity="many">%1$s - %2$d изменений в расписании</item>
+        <item quantity="other">%1$s - %2$d  изменений в расписании</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d - изменений в расписании</item>
+        <item quantity="few">%1$d изменение в расписании</item>
+        <item quantity="many">%1$d изменение в расписании</item>
+        <item quantity="other">%1$d изменений в расписании</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d изменение</item>
+        <item quantity="few">%d изменение</item>
+        <item quantity="many">%d изменение</item>
+        <item quantity="other">%d изменений</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Проведённые уроки</string>
     <string name="completed_lessons_button">Просмотреть проведённые уроки</string>
@@ -194,6 +222,24 @@
     <string name="attendance_excuse_success">Запрос на освобождение оправдания успешно отправлен!</string>
     <string name="attendance_excuse_no_selection">Выберите хотя-бы одно отсутствие</string>
     <string name="attendance_excuse_title">Изменить статус</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">Новое посещение</item>
+        <item quantity="few">Новое посещение</item>
+        <item quantity="many">Новое посещение</item>
+        <item quantity="other">Новое посещение</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d новое посещения</item>
+        <item quantity="few">%1$d новое посещение</item>
+        <item quantity="many">%1$d новое посещение</item>
+        <item quantity="other">%1$d новое посещения</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d посещаемость</item>
+        <item quantity="few">%d посещаемость</item>
+        <item quantity="many">%d посещаемость</item>
+        <item quantity="other">%d посещаемость</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Общая</string>
     <!--Exam-->
@@ -328,6 +374,9 @@
     <string name="homework_no_items">Нет домашних заданий</string>
     <string name="homework_mark_as_done">Отметить как выполненное</string>
     <string name="homework_mark_as_undone">Отметить как невыполненное</string>
+    <string name="homework_add">Добавить домашнюю работу</string>
+    <string name="homework_add_success">Домашняя работа успешно добавлена</string>
+    <string name="homework_delete_success">Домашняя работа успешно удалена</string>
     <string name="homework_attachments">Вложения</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Новая домашняя работа</item>
@@ -499,6 +548,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Уроки</string>
     <string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Сегодня и завтра)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Сейчас:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string>
     <string name="dashboard_timetable_first_lesson_title_first">Первый:</string>
@@ -594,6 +644,10 @@
     <string name="all_no">Нет</string>
     <string name="all_save">Сохранить</string>
     <string name="all_title">Тема</string>
+    <string name="all_add">Добавить</string>
+    <string name="all_copied">Скопировано</string>
+    <string name="all_undo">Отменить</string>
+    <string name="all_change">Изменить</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Нет уроков</string>
     <string name="widget_timetable_theme_title">Выбрать тему</string>
@@ -601,13 +655,13 @@
     <string name="widget_timetable_theme_dark">Тёмная</string>
     <string name="widget_timetable_theme_system">Тема системы</string>
     <!--Preferences-->
-    <string name="pref_view_header">Внешний вид приложения &amp; поведение</string>
+    <string name="pref_view_header">Приложение</string>
     <string name="pref_view_list">Окно по умолчанию</string>
     <string name="pref_view_grade_average_mode">Рассчитанные средние параметры</string>
     <string name="pref_view_grade_average_force_calc">Принудительно высчитать среднюю оценку через приложение</string>
     <string name="pref_view_present">Показать присутствие</string>
     <string name="pref_view_app_theme">Тема</string>
-    <string name="pref_view_expand_grade">Разворачивать оценки</string>
+    <string name="pref_view_expand_grade">Расширяется оценка</string>
     <string name="pref_view_timetable_show_timers">Отметить текущий урок</string>
     <string name="pref_view_timetable_show_groups">Показать группы рядом с темами</string>
     <string name="pref_view_grade_statistics_list">Показывать диаграммы в оценках класса</string>
@@ -616,6 +670,7 @@
     <string name="pref_view_grade_sorting_mode">Сортировка уроков</string>
     <string name="pref_view_app_language">Язык</string>
     <string name="pref_notify_header">Уведомления</string>
+    <string name="pref_notify_header_other">Прочее</string>
     <string name="pref_notify_switch">Показывать уведомления</string>
     <string name="pref_notify_upcoming_lessons_switch">Показывать уведомления о будущих уроках</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Сделать уведомления о предстоящем уроке постоянным</string>
@@ -644,16 +699,25 @@
     <string name="pref_other_grade_modifier_minus">Стоимость минуса</string>
     <string name="pref_other_fill_message_content">Отвечать с историей сообщений</string>
     <string name="pref_other_optional_arithmetic_average">Показывать среднее арифметическое при отсутствии весов</string>
+    <string name="pref_ads_support_category_name">Поддержка</string>
+    <string name="pref_ads_support">Смотреть одиночную рекламу для поддержки проекта</string>
+    <string name="pref_ads_privacy_title">Согласие на обработку данных</string>
+    <string name="pref_ads_privacy_description">Для просмотра рекламы вы должны согласиться с условиями обработки данных нашей Политики конфиденциальности</string>
+    <string name="pref_ads_privacy_agree">Согласен</string>
+    <string name="pref_ads_privacy_link">Политика конфиденциальности</string>
+    <string name="pref_ads_loading">Объявление загружается</string>
     <string name="pref_settings_advanced_title">Расширенные</string>
     <string name="pref_settings_appearance_title">Внешний вид &amp; Поведение</string>
     <string name="pref_settings_notifications_title">Уведомления</string>
     <string name="pref_settings_sync_title">Синхронизация</string>
+    <string name="pref_settings_ads_title">Реклама</string>
     <string name="pref_grades_appearance_header">Оценки</string>
     <string name="pref_dashboard_appearance_header">Панель</string>
     <string name="pref_dashboard_appearance_tiles_title">Видимость плиток</string>
     <string name="pref_attendance_appearance_view">Посещаемость</string>
     <string name="pref_timetable_appearance_view">Расписание</string>
     <string name="pref_grades_advanced_header">Оценки</string>
+    <string name="pref_counted_average_advanced_header">Расчетное среднее</string>
     <string name="pref_messages_advanced_header">Сообщения</string>
     <string name="pref_appearance_category">Внешний вид &amp; Поведение</string>
     <string name="pref_appearance_category_summary">Языки, темы, темы сортировки темы</string>
@@ -663,7 +727,8 @@
     <string name="pref_sync_category_summary">Автоматическое обновление, интервал синхронизации</string>
     <string name="pref_advanced_category_summary">Значения плюс и минус, средний расчет</string>
     <string name="pref_advanced_category">Расширенные</string>
-    <string name="pref_about_category_summary">Версия приложения, участники, социальные порталы, лицензии</string>
+    <string name="pref_about_category_summary">Версия приложения, участники, социальные порталы</string>
+    <string name="pref_ads_category_summary">Отображение объявлений, поддержка проекта</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Новые оценки</string>
     <string name="channel_new_homework">Новая домашняя работа</string>
@@ -676,6 +741,8 @@
     <string name="channel_push">Показывать push-уведомления</string>
     <string name="channel_upcoming_lessons">Будущие уроки</string>
     <string name="channel_debug">Дебаг</string>
+    <string name="channel_change_timetable">Изменение расписания</string>
+    <string name="channel_new_attendance">Новое посещение</string>
     <!--Colors-->
     <string name="all_black">Чёрный</string>
     <string name="all_red">Красный</string>
@@ -683,10 +750,6 @@
     <string name="all_green">Зелёный</string>
     <string name="all_purple">Фиолетовый</string>
     <string name="all_empty_color">Нет цвета</string>
-    <!--Others-->
-    <string name="all_copied">Скопировано</string>
-    <string name="all_undo">Отменить</string>
-    <string name="all_change">Изменить</string>
     <!--Update helper-->
     <string name="update_download_started">Загрузка обновлений началась…</string>
     <string name="update_download_success">Только что было скачано обновление.</string>
@@ -703,4 +766,5 @@
     <string name="error_unknown">Произошла неожиданная ошибка</string>
     <string name="error_feature_disabled">Функция была выключена школой</string>
     <string name="error_feature_not_available">Функция не доступна в этом режиме</string>
+    <string name="error_field_required">Это поле является обязательным</string>
 </resources>
diff --git a/app/src/main/res/values-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml
index b8974f23..e64f5606 100644
--- a/app/src/main/res/values-sk/preferences_values.xml
+++ b/app/src/main/res/values-sk/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Farby známok v denníku</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>Až 1 naraz</item>
+        <item>Vždy rozbalené</item>
+        <item>Neobmedzené rozbalené</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Priemer známok iba z vybraného semestra</item>
         <item>Priemer z priemerov z oboch semestrov</item>
@@ -48,7 +53,7 @@
     <string-array name="dashboard_tile_entries">
         <item>Šťastné číslo</item>
         <item>Neprečítané správy</item>
-        <item>Dochádzka</item>
+        <item>Frekvencia</item>
         <item>Lekcie</item>
         <item>Známky</item>
         <item>Domáce úlohy</item>
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 19e9735f..fc459eaf 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -4,7 +4,7 @@
     <string name="login_title">Prihlásenie</string>
     <string name="main_title">Wulkanowy</string>
     <string name="grade_title">Známky</string>
-    <string name="attendance_title">Dochádzka</string>
+    <string name="attendance_title">Frekvencia</string>
     <string name="exam_title">Skúšky</string>
     <string name="timetable_title">Plán lekcie</string>
     <string name="settings_title">Nastavenia</string>
@@ -13,10 +13,11 @@
     <string name="logviewer_title">Prehliadač protokolov</string>
     <string name="debug_title">Ladenie</string>
     <string name="notification_debug_title">Ladenie upozornení</string>
-    <string name="contributors_title">Prispievatelia</string>
+    <string name="contributors_title">Tvorcovia</string>
     <string name="license_title">Licencie</string>
     <string name="message_title">Správy</string>
     <string name="send_message_title">Nová správa</string>
+    <string name="add_homework_title">Nový domáci úloh</string>
     <string name="note_title">Poznámky a úspechy</string>
     <string name="homework_title">Domáce úlohy</string>
     <string name="account_title">Manažér účtov</string>
@@ -29,7 +30,7 @@
     <string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Prihláste sa pomocou študentského alebo rodičovského konta</string>
-    <string name="login_header_symbol">Zadajte symbol zo stránky denníka</string>
+    <string name="login_header_symbol">Zadajte symbol zo stránky denníka: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Užívateľské meno</string>
     <string name="login_email_hint">Email</string>
     <string name="login_login_pesel_email_hint">Prihlásenie, číslo PESEL alebo e-mail</string>
@@ -53,15 +54,14 @@
     <string name="login_invalid_custom_email">Použite priradené prihlasovacie alebo e-mail v @%1$s</string>
     <string name="login_invalid_symbol">Neplatný symbol</string>
     <string name="login_incorrect_symbol">Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+</string>
-    <string name="login_field_required">Toto pole je povinné</string>
     <string name="login_duplicate_student">Vybraný žiak už je prihlásený</string>
     <string name="login_symbol_helper">Symbol nájdete na stránke denníka v &#160;<b>Uczeń</b>→&#160;<b>Dostęp Mobilny</b> →&#160;<b>Zarejestruj urządzenie mobilne</b>.\n\nUistite sa, že ste na predchádzajúcu obrazovke nastaviť správny variant denníka do poľa <b>Variácie denníka UONET+</b>. Wulkanowy v túto chvíľu nezistí predškolské żaków</string>
     <string name="login_select_student">Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť</string>
     <string name="login_advanced">Iné možnosti</string>
-    <string name="login_advanced_warning_mobile_api">V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie dochádzky, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení</string>
+    <string name="login_advanced_warning_mobile_api">V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie frekvencií, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení</string>
     <string name="login_advanced_warning_scraper">Tento režim zobrazuje rovnaké dáta, ktoré sa zobrazujú na webových stránkach denníka</string>
     <string name="login_advanced_warning_hybrid">Kombinácia najlepších vlastností ostatných dvoch režimov. Funguje rýchlejšie ako scraper a poskytuje funkcie, ktoré nie sú k dispozícii v režime Mobilne API. Je to v experimentálnej fáze</string>
-    <string name="login_privacy_policy">Zásady ochrany osobných údajov</string>
+    <string name="login_privacy_policy">Ochrana osobných údajov</string>
     <string name="login_contact_header">Problémy s prihlásením? Napíšte nám!</string>
     <string name="login_contact_email">Email</string>
     <string name="login_contact_discord">Discord</string>
@@ -165,6 +165,34 @@
     <string name="timetable_now">Teraz: %s</string>
     <string name="timetable_next">Za chvíľu: %s</string>
     <string name="timetable_later">Neskôr: %s</string>
+    <string name="timetable_notify_lesson">%1$s lekcia %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Zmena učebne z %1$s na %2$s</string>
+    <string name="timetable_notify_change_teacher">Zmena učiteľa z %1$s na %2$s</string>
+    <string name="timetable_notify_change_subject">Zmena predmetu z %1$s na %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Zmena plánu lekcií</item>
+        <item quantity="few">Zmeny plánu lekcií</item>
+        <item quantity="many">Zmeny plánu lekcií</item>
+        <item quantity="other">Zmeny plánu lekcií</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d zmena plánu lekcií</item>
+        <item quantity="few">%1$s - %2$d zmeny plánu lekcií</item>
+        <item quantity="many">%1$s - %2$d zmien plánu lekcií</item>
+        <item quantity="other">%1$s - %2$d zmien plánu lekcií</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d zmena plánu lekcií</item>
+        <item quantity="few">%1$d zmeny plánu lekcií</item>
+        <item quantity="many">%1$d zmien plánu lekcií</item>
+        <item quantity="other">%1$d zmien plánu lekcií</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d zmena</item>
+        <item quantity="few">%d zmeny</item>
+        <item quantity="many">%d zmien</item>
+        <item quantity="other">%d zmien</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Dokončené lekcie</string>
     <string name="completed_lessons_button">Zobraziť dokončené lekcie</string>
@@ -173,11 +201,11 @@
     <string name="completed_lessons_absence">Neprítomnosť</string>
     <string name="completed_lessons_resources">Zdroje</string>
     <!--Additional lessons-->
-    <string name="additional_lessons_title">Ďalší lekcie</string>
+    <string name="additional_lessons_title">Ďalšie lekcie</string>
     <string name="additional_lessons_button">Zobraziť ďalšie lekcie</string>
     <string name="additional_lessons_no_items">Žiadne informácie o ďalších lekciách</string>
     <!--Attendance-->
-    <string name="attendance_summary_button">Zhrnutie dochádzky</string>
+    <string name="attendance_summary_button">Zhrnutie frekvencií</string>
     <string name="attendance_absence_school">Neprítomnosť zo školských dôvodov</string>
     <string name="attendance_absence_excused">Ospravedlnená neprítomnosť</string>
     <string name="attendance_absence_unexcused">Neospravedlnená neprítomnosť</string>
@@ -194,6 +222,24 @@
     <string name="attendance_excuse_success">Žiadosť o ospravedlnenie neprítomnosti bola úspešne odoslaná!</string>
     <string name="attendance_excuse_no_selection">Musíte vybrať aspoň jednu neprítomnosť!</string>
     <string name="attendance_excuse_title">Ospravedlniť</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">Nová frekvencia</item>
+        <item quantity="few">Nové frekvencie</item>
+        <item quantity="many">Nové frekvencie</item>
+        <item quantity="other">Nové frekvencie</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d nová frekvencia</item>
+        <item quantity="few">%1$d nové frekvencie</item>
+        <item quantity="many">%1$d nových frekvencií</item>
+        <item quantity="other">%1$d nových frekvencií</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d frekvencia</item>
+        <item quantity="few">%d frekvencie</item>
+        <item quantity="many">%d frekvencií</item>
+        <item quantity="other">%d frekvencií</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Spoločne</string>
     <!--Exam-->
@@ -328,6 +374,9 @@
     <string name="homework_no_items">Žiadne informácie o domácich úlohách</string>
     <string name="homework_mark_as_done">Označiť ako hotové</string>
     <string name="homework_mark_as_undone">Nevyrobené</string>
+    <string name="homework_add">Pridať domácu úlohu</string>
+    <string name="homework_add_success">Domáca úloha bola úspešně pridaná</string>
+    <string name="homework_delete_success">Domáca úloha bola úspešně odstránená</string>
     <string name="homework_attachments">Prílohy</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Nový domáci úloh</item>
@@ -442,7 +491,7 @@
     <string name="account_personal_data">Osobné údaje</string>
     <!--About-->
     <string name="about_version">Verzia aplikácie</string>
-    <string name="about_contributor">Prispievatelia</string>
+    <string name="about_contributor">Tvorcovia</string>
     <string name="about_contributor_summary">Zoznam vývojárov Wulkanového</string>
     <string name="about_feedback">Nahlásiť chybu</string>
     <string name="about_feedback_summary">Odoslať správu o chybe e-mailom</string>
@@ -450,11 +499,11 @@
     <string name="about_faq_summary">Prečítajte si často kladené otázky</string>
     <string name="about_discord">Server Discord</string>
     <string name="about_discord_summary">Pripojte sa ku komunite Wulkanového</string>
-    <string name="about_facebook">Facebooková fanpage</string>
+    <string name="about_facebook">Stránka na Facebooku</string>
     <string name="about_twitter">Twitter stránka</string>
     <string name="about_twitter_summary">Sledujte nás na Twitteri</string>
-    <string name="about_facebook_summary">Rovnako ako naše facebooková fanpage</string>
-    <string name="about_privacy">Zásady ochrany osobných údajov</string>
+    <string name="about_facebook_summary">Dajte like našej stránke na Facebooku</string>
+    <string name="about_privacy">Ochrana osobných údajov</string>
     <string name="about_privacy_summary">Pravidlá pre zhromažďovanie osobných údajov</string>
     <string name="about_system">Systémové nastavenia</string>
     <string name="about_system_summary">Otvoriť systémové nastavenia</string>
@@ -499,6 +548,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Lekcie</string>
     <string name="dashboard_timetable_title_tomorrow">(Zajtra)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Dnes a zajtra)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Za chvíľu:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string>
     <string name="dashboard_timetable_first_lesson_title_first">Prvá:</string>
@@ -594,6 +644,10 @@
     <string name="all_no">Nie</string>
     <string name="all_save">Uložiť</string>
     <string name="all_title">Titul</string>
+    <string name="all_add">Pridať</string>
+    <string name="all_copied">Skopírované</string>
+    <string name="all_undo">Vrátiť</string>
+    <string name="all_change">Zmeniť</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Žiadne lekcie</string>
     <string name="widget_timetable_theme_title">Vybrať motív</string>
@@ -601,13 +655,13 @@
     <string name="widget_timetable_theme_dark">Tmavý</string>
     <string name="widget_timetable_theme_system">Motív systému</string>
     <!--Preferences-->
-    <string name="pref_view_header">Vzhľad a správanie aplikácií</string>
+    <string name="pref_view_header">Aplikácia</string>
     <string name="pref_view_list">Predvolené zobrazenie</string>
     <string name="pref_view_grade_average_mode">Možnosti vypočítaného priemeru</string>
     <string name="pref_view_grade_average_force_calc">Vynútiť priemerný výpočet podľa aplikácie</string>
     <string name="pref_view_present">Zobraziť prítomnosť</string>
     <string name="pref_view_app_theme">Motív</string>
-    <string name="pref_view_expand_grade">Rozbaliť známky</string>
+    <string name="pref_view_expand_grade">Rozvijanie známok</string>
     <string name="pref_view_timetable_show_timers">Označiť aktuálne lekciu</string>
     <string name="pref_view_timetable_show_groups">Zobraziť skupiny vedľa predmetov</string>
     <string name="pref_view_grade_statistics_list">Zobraziť zoznam grafov v známkach triedy</string>
@@ -616,6 +670,7 @@
     <string name="pref_view_grade_sorting_mode">Triedenie predmetov</string>
     <string name="pref_view_app_language">Jazyk</string>
     <string name="pref_notify_header">Upozornenia</string>
+    <string name="pref_notify_header_other">Iné</string>
     <string name="pref_notify_switch">Zobraziť upozornenia</string>
     <string name="pref_notify_upcoming_lessons_switch">Zobraziť upozornenia o nadchádzajúcej lekciu</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Nastaviť upozornenia o nadchádzajúcej lekcii ako trvalé</string>
@@ -644,16 +699,25 @@
     <string name="pref_other_grade_modifier_minus">Hodnota mínusu</string>
     <string name="pref_other_fill_message_content">Odpovedať s históriou správ</string>
     <string name="pref_other_optional_arithmetic_average">Vypočítať aritmetický priemer, ak žiadna známka nemá váhu</string>
+    <string name="pref_ads_support_category_name">Podpora</string>
+    <string name="pref_ads_support">Pozrite sa na jednu reklamu pre podporu projektu</string>
+    <string name="pref_ads_privacy_title">Súhlas so spracovaním dát</string>
+    <string name="pref_ads_privacy_description">Ak chcete sledovať reklamu, musíte súhlasiť s podmienkami spracovania údajov v našich Zásadách Ochrany Osobných Údajov</string>
+    <string name="pref_ads_privacy_agree">Súhlasím</string>
+    <string name="pref_ads_privacy_link">Ochrana osobných údajov</string>
+    <string name="pref_ads_loading">Reklama sa načítava</string>
     <string name="pref_settings_advanced_title">Pokročilé</string>
     <string name="pref_settings_appearance_title">Vzhľad a správanie</string>
     <string name="pref_settings_notifications_title">Upozornenia</string>
     <string name="pref_settings_sync_title">Synchronizácia</string>
+    <string name="pref_settings_ads_title">Reklamy</string>
     <string name="pref_grades_appearance_header">Známky</string>
     <string name="pref_dashboard_appearance_header">Domov</string>
     <string name="pref_dashboard_appearance_tiles_title">Viditeľnosť dlaždíc</string>
-    <string name="pref_attendance_appearance_view">Dochádzka</string>
+    <string name="pref_attendance_appearance_view">Frekvencia</string>
     <string name="pref_timetable_appearance_view">Plán lekcie</string>
     <string name="pref_grades_advanced_header">Známky</string>
+    <string name="pref_counted_average_advanced_header">Vypočítaný priemer</string>
     <string name="pref_messages_advanced_header">Správy</string>
     <string name="pref_appearance_category">Vzhľad a správanie</string>
     <string name="pref_appearance_category_summary">Jazyky, motívy, triedenie predmetov</string>
@@ -663,7 +727,8 @@
     <string name="pref_sync_category_summary">Automatická aktualizácia, interval aktualizácií</string>
     <string name="pref_advanced_category_summary">Hodnota plusu a mínusu, výpočet priemeru</string>
     <string name="pref_advanced_category">Pokročilé</string>
-    <string name="pref_about_category_summary">Verzia aplikácie, prispievatelia, sociálne portály, licencie</string>
+    <string name="pref_about_category_summary">Verzia aplikácie, tvorcovia, sociálne portály</string>
+    <string name="pref_ads_category_summary">Zobrazovanie reklám, podpora projektu</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Nové známky</string>
     <string name="channel_new_homework">Nové domáce úlohy</string>
@@ -676,6 +741,8 @@
     <string name="channel_push">Push upozornenia</string>
     <string name="channel_upcoming_lessons">Nadchádzajúce lekcie</string>
     <string name="channel_debug">Ladenie</string>
+    <string name="channel_change_timetable">Zmeny plánu lekcií</string>
+    <string name="channel_new_attendance">Nové frekvencie</string>
     <!--Colors-->
     <string name="all_black">Čierna</string>
     <string name="all_red">Červená</string>
@@ -683,10 +750,6 @@
     <string name="all_green">Zelená</string>
     <string name="all_purple">Fialová</string>
     <string name="all_empty_color">Žiadna farba</string>
-    <!--Others-->
-    <string name="all_copied">Skopírované</string>
-    <string name="all_undo">Vrátiť</string>
-    <string name="all_change">Zmeniť</string>
     <!--Update helper-->
     <string name="update_download_started">Sťahovanie aktualizácií začalo…</string>
     <string name="update_download_success">Aktualizácia bola stiahnutá.</string>
@@ -703,4 +766,5 @@
     <string name="error_unknown">Vyskytla sa neočakávaná chyba</string>
     <string name="error_feature_disabled">Funkcia je deaktivovaná cez vašou školou</string>
     <string name="error_feature_not_available">Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API</string>
+    <string name="error_field_required">Toto pole je povinné</string>
 </resources>
diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml
index f21ad819..a8c09bcf 100644
--- a/app/src/main/res/values-uk/preferences_values.xml
+++ b/app/src/main/res/values-uk/preferences_values.xml
@@ -40,6 +40,11 @@
         <item>Wulkanowy</item>
         <item>Кольори оцінок в щоденнику</item>
     </string-array>
+    <string-array name="default_expand_grade_entries">
+        <item>Раз до 1</item>
+        <item>Завжди розгорнутий</item>
+        <item>Необмежена кількість розширень</item>
+    </string-array>
     <string-array name="grade_average_mode_entries">
         <item>Середні оцінки тільки від обраного семестру</item>
         <item>Середнє значення для обох семестів</item>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 74175548..af774cdd 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -17,6 +17,7 @@
     <string name="license_title">Ліцензії</string>
     <string name="message_title">Повідомлення</string>
     <string name="send_message_title">Нове повідомлення</string>
+    <string name="add_homework_title">Нова домашня робота</string>
     <string name="note_title">Нотатки та досягнення</string>
     <string name="homework_title">Домашні завдання</string>
     <string name="account_title">Менеджер аккаунтів</string>
@@ -29,7 +30,7 @@
     <string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
     <!--Login-->
     <string name="login_header_default">Авторизуйтеся за допомогою аккаунта учня або батьків</string>
-    <string name="login_header_symbol">Введіть символ зі сторінки реєстру</string>
+    <string name="login_header_symbol">Введіть символ зі сторінки реєстру: &lt;b&gt;%1$s&lt;/b&gt;</string>
     <string name="login_nickname_hint">Ім\'я користувача</string>
     <string name="login_email_hint">Електронна пошта</string>
     <string name="login_login_pesel_email_hint">Логін, PESEL або електронна пошта</string>
@@ -53,7 +54,6 @@
     <string name="login_invalid_custom_email">Використовуйте призначений логін або електронну адресу в @%1$s</string>
     <string name="login_invalid_symbol">Неправильний симбвол</string>
     <string name="login_incorrect_symbol">Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+</string>
-    <string name="login_field_required">Обов\'язкове поле</string>
     <string name="login_duplicate_student">Даного учня вже авторизовано</string>
     <string name="login_symbol_helper">Символ можна знайти на сторінці реєстру в &#160; <b> Учень </b> → &#160; <b> Мобільний доступ </b> → &#160; <b> Додайте мобільне приладдя </b>.\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі <b> UONET + варіант реєстрації </b> на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів</string>
     <string name="login_select_student">Виберіть учнів для авторизації в додатку</string>
@@ -165,6 +165,34 @@
     <string name="timetable_now">Зараз: %s</string>
     <string name="timetable_next">Наступний: %s</string>
     <string name="timetable_later">Пізніше: %s</string>
+    <string name="timetable_notify_lesson">%1$s урок %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Зміна місця з %1$s на %2$s</string>
+    <string name="timetable_notify_change_teacher">Змінити вчителя з %1$s на %2$s</string>
+    <string name="timetable_notify_change_subject">Зміна теми з %1$s на %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Зміна у розкладі</item>
+        <item quantity="few">Зміна у розкладі</item>
+        <item quantity="many">Зміна у розкладі</item>
+        <item quantity="other">Зміни у розкладі рейсу</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d зміна в розкладі</item>
+        <item quantity="few">%1$s - %2$d зміна в розкладі</item>
+        <item quantity="many">%1$s - %2$d зміна в розкладі</item>
+        <item quantity="other">%1$s - %2$d  змін у розкладі</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d зміна в розкладі</item>
+        <item quantity="few">%1$d зміна в розкладі</item>
+        <item quantity="many">%1$d зміна в розкладі</item>
+        <item quantity="other">%1$d змін у розкладі</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d зміна</item>
+        <item quantity="few">%d зміна</item>
+        <item quantity="many">%d зміна</item>
+        <item quantity="other">%d змін</item>
+    </plurals>
     <!--Completed lessons-->
     <string name="completed_lessons_title">Уроки, що відбулися</string>
     <string name="completed_lessons_button">Показати уроки, що відбулися</string>
@@ -194,6 +222,24 @@
     <string name="attendance_excuse_success">Запит на виправдання відсутності успішно надіслано!</string>
     <string name="attendance_excuse_no_selection">Оберіть хоча б одну відсутність</string>
     <string name="attendance_excuse_title">Змінити статус</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">Нова відвідуваність</item>
+        <item quantity="few">Нова відвідуваність</item>
+        <item quantity="many">Нова відвідуваність</item>
+        <item quantity="other">Нова відвідуваність</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d новий відвідувач</item>
+        <item quantity="few">%1$d новий відвідувач</item>
+        <item quantity="many">%1$d новий відвідувач</item>
+        <item quantity="other">%1$d відвідування</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d відвідування</item>
+        <item quantity="few">%d відвідування</item>
+        <item quantity="many">%d відвідування</item>
+        <item quantity="other">%d відвідування</item>
+    </plurals>
     <!--Attendance summary-->
     <string name="attendance_summary_total">Загальна</string>
     <!--Exam-->
@@ -328,6 +374,9 @@
     <string name="homework_no_items">Брак домашніх завдань</string>
     <string name="homework_mark_as_done">Позначити як зроблене</string>
     <string name="homework_mark_as_undone">Позначити як не зроблене</string>
+    <string name="homework_add">Додати домашню роботу</string>
+    <string name="homework_add_success">Домашню роботу додано успішно</string>
+    <string name="homework_delete_success">Домашню роботу видалено успішно</string>
     <string name="homework_attachments">Додатки</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">Нова домашня робота</item>
@@ -499,6 +548,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Уроки</string>
     <string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(сьогодні та завтра)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">Через мить:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string>
     <string name="dashboard_timetable_first_lesson_title_first">Перше:</string>
@@ -594,6 +644,10 @@
     <string name="all_no">Ні</string>
     <string name="all_save">Зберегти</string>
     <string name="all_title">Титул</string>
+    <string name="all_add">Додати</string>
+    <string name="all_copied">Скопійовано</string>
+    <string name="all_undo">Відмінити</string>
+    <string name="all_change">Змінити</string>
     <!--Timetable Widget-->
     <string name="widget_timetable_no_items">Брак уроків</string>
     <string name="widget_timetable_theme_title">Увібрати тему</string>
@@ -601,13 +655,13 @@
     <string name="widget_timetable_theme_dark">Темна</string>
     <string name="widget_timetable_theme_system">Тема системи</string>
     <!--Preferences-->
-    <string name="pref_view_header">Поява додатка &amp; amp; поведінки</string>
+    <string name="pref_view_header">Додатки</string>
     <string name="pref_view_list">Вікно за замовчуванням</string>
     <string name="pref_view_grade_average_mode">Розрахункові середні параметри</string>
     <string name="pref_view_grade_average_force_calc">Примусово розрахувати середню оцінку через додаток</string>
     <string name="pref_view_present">Показати присутність</string>
     <string name="pref_view_app_theme">Тема</string>
-    <string name="pref_view_expand_grade">Більше оцінок</string>
+    <string name="pref_view_expand_grade">Розширення оцінок</string>
     <string name="pref_view_timetable_show_timers">Позначити поточний урок</string>
     <string name="pref_view_timetable_show_groups">Показувати групи поруч з темами</string>
     <string name="pref_view_grade_statistics_list">Показувати діаграми в оцінках класу</string>
@@ -616,6 +670,7 @@
     <string name="pref_view_grade_sorting_mode">Сортування предметів</string>
     <string name="pref_view_app_language">Мова</string>
     <string name="pref_notify_header">Повідомлення</string>
+    <string name="pref_notify_header_other">Інше</string>
     <string name="pref_notify_switch">Показувати повідомлення</string>
     <string name="pref_notify_upcoming_lessons_switch">Показувати повідомлення о наступних уроках</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Зробити сповіщення майбутнього уроку нестійкими</string>
@@ -644,16 +699,25 @@
     <string name="pref_other_grade_modifier_minus">Вага мінуса</string>
     <string name="pref_other_fill_message_content">Відповісти з історією повідомлень</string>
     <string name="pref_other_optional_arithmetic_average">Показувати в середньому арифметику, якщо немає ваги</string>
+    <string name="pref_ads_support_category_name">Підтримка</string>
+    <string name="pref_ads_support">Відстежуйте єдину рекламу для підтримки проекту</string>
+    <string name="pref_ads_privacy_title">Згода в обробці даних</string>
+    <string name="pref_ads_privacy_description">Щоб переглянути рекламу, ви повинні погодитися з умовами обробки даних нашої Політики конфіденційності</string>
+    <string name="pref_ads_privacy_agree">Погоджуюсь</string>
+    <string name="pref_ads_privacy_link">Політика конфіденційності</string>
+    <string name="pref_ads_loading">Реклама завантажується</string>
     <string name="pref_settings_advanced_title">Додатково</string>
     <string name="pref_settings_appearance_title">Вигляд &amp; Поведінка</string>
     <string name="pref_settings_notifications_title">Повідомлення</string>
     <string name="pref_settings_sync_title">Синхронізація</string>
+    <string name="pref_settings_ads_title">Реклама</string>
     <string name="pref_grades_appearance_header">Оцінки</string>
     <string name="pref_dashboard_appearance_header">Дошка</string>
     <string name="pref_dashboard_appearance_tiles_title">Видимість плиток</string>
     <string name="pref_attendance_appearance_view">Відвідуваність</string>
     <string name="pref_timetable_appearance_view">Розклад</string>
     <string name="pref_grades_advanced_header">Класи</string>
+    <string name="pref_counted_average_advanced_header">Обчислена середня</string>
     <string name="pref_messages_advanced_header">Повідомлення</string>
     <string name="pref_appearance_category">Вигляд &amp; Поведінка</string>
     <string name="pref_appearance_category_summary">Мови, теми, тема сортування</string>
@@ -663,7 +727,8 @@
     <string name="pref_sync_category_summary">Автоматичне оновлення, інтервал синхронізації</string>
     <string name="pref_advanced_category_summary">Плюс і мінус значення, середні обчислення</string>
     <string name="pref_advanced_category">Додатково</string>
-    <string name="pref_about_category_summary">Версія програми, учасники, соціальні портали, ліцензії</string>
+    <string name="pref_about_category_summary">Версія програми, учасники, соціальні портали</string>
+    <string name="pref_ads_category_summary">Відображається реклама, підтримка проектів</string>
     <!--Notification Channels-->
     <string name="channel_new_grades">Нові оцінки</string>
     <string name="channel_new_homework">Нова домашня робота</string>
@@ -676,6 +741,8 @@
     <string name="channel_push">Показувати push-повідомлення</string>
     <string name="channel_upcoming_lessons">Наступні уроки</string>
     <string name="channel_debug">Дебаг</string>
+    <string name="channel_change_timetable">Зміна у розкладі</string>
+    <string name="channel_new_attendance">Нова відвідуваність</string>
     <!--Colors-->
     <string name="all_black">Чорний</string>
     <string name="all_red">Червоний</string>
@@ -683,10 +750,6 @@
     <string name="all_green">Зелений</string>
     <string name="all_purple">Фіолетовий</string>
     <string name="all_empty_color">Брак кольору</string>
-    <!--Others-->
-    <string name="all_copied">Скопійовано</string>
-    <string name="all_undo">Відмінити</string>
-    <string name="all_change">Змінити</string>
     <!--Update helper-->
     <string name="update_download_started">Завантаження оновлень розпочато…</string>
     <string name="update_download_success">Щойно завантажено оновлення.</string>
@@ -703,4 +766,5 @@
     <string name="error_unknown">Відбулася несподівана помилка</string>
     <string name="error_feature_disabled">Функція вимкнена школою</string>
     <string name="error_feature_not_available">Функція не доступна в цьому режимі</string>
+    <string name="error_field_required">Це поле обов\'язкове для заповнення</string>
 </resources>
diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml
index 574e8488..840f5357 100644
--- a/app/src/main/res/values-v23/styles.xml
+++ b/app/src/main/res/values-v23/styles.xml
@@ -1,14 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+
     <style name="WulkanowyTheme" parent="BaseWulkanowyTheme">
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:statusBarColor">@android:color/white</item>
     </style>
-
-    <style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
-        <item name="android:windowBackground">@drawable/layer_splash_background</item>
-        <item name="android:statusBarColor">@color/colorPrimaryDark</item>
-        <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
-        <item name="android:windowLightStatusBar">false</item>
-    </style>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-v26/styles.xml b/app/src/main/res/values-v26/styles.xml
index 55413c05..3fb0a5dd 100644
--- a/app/src/main/res/values-v26/styles.xml
+++ b/app/src/main/res/values-v26/styles.xml
@@ -5,11 +5,4 @@
         <item name="android:windowLightStatusBar">false</item>
         <item name="android:statusBarColor">@android:color/darker_gray</item>
     </style>
-
-    <style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
-        <item name="android:windowBackground">@drawable/layer_splash_background</item>
-        <item name="android:statusBarColor">@color/colorPrimaryDark</item>
-        <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
-        <item name="android:windowLightStatusBar">false</item>
-    </style>
 </resources>
diff --git a/app/src/main/res/values-v28/styles.xml b/app/src/main/res/values-v28/styles.xml
index ee77091d..a936566f 100644
--- a/app/src/main/res/values-v28/styles.xml
+++ b/app/src/main/res/values-v28/styles.xml
@@ -6,12 +6,4 @@
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:statusBarColor">@android:color/white</item>
     </style>
-
-    <style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
-        <item name="android:windowBackground">@drawable/layer_splash_background</item>
-        <item name="android:statusBarColor">@color/colorPrimaryDark</item>
-        <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
-        <item name="android:windowLightNavigationBar">false</item>
-        <item name="android:windowLightStatusBar">false</item>
-    </style>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-v29/styles.xml b/app/src/main/res/values-v29/styles.xml
index ee77091d..a936566f 100644
--- a/app/src/main/res/values-v29/styles.xml
+++ b/app/src/main/res/values-v29/styles.xml
@@ -6,12 +6,4 @@
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:statusBarColor">@android:color/white</item>
     </style>
-
-    <style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
-        <item name="android:windowBackground">@drawable/layer_splash_background</item>
-        <item name="android:statusBarColor">@color/colorPrimaryDark</item>
-        <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
-        <item name="android:windowLightNavigationBar">false</item>
-        <item name="android:windowLightStatusBar">false</item>
-    </style>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml
index dac94c3f..15849047 100644
--- a/app/src/main/res/values/api_hosts.xml
+++ b/app/src/main/res/values/api_hosts.xml
@@ -40,7 +40,7 @@
         <item>https://vulcan.net.pl/?login</item>
         <item>https://vulcan.net.pl/?login</item>
         <item>https://vulcan.net.pl/?login</item>
-        <item>http://fakelog.tk/?email</item>
+        <item>http://fakelog.cf/?email</item>
     </string-array>
     <string-array name="hosts_symbols">
         <item>Default</item>
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 49ef39ab..d4ed6e97 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -3,4 +3,5 @@
     <attr name="colorDivider" format="color" />
     <attr name="colorSwipeRefresh" format="color" />
     <attr name="colorTimetableChange" format="color" />
+    <attr name="colorMessageMedium" format="color" />
 </resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index a68e2710..f3112b10 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -12,6 +12,9 @@
     <color name="colorStatusBarLight">#1C1C1C</color>
     <color name="colorStatusBarBlack">#0D0D0D</color>
 
+    <color name="dashboard_message_medium_light">#FFD980</color>
+    <color name="dashboard_message_medium_dark">#ffd54f</color>
+
     <color name="timetable_change_light">#ffd54f</color>
     <color name="timetable_change_dark">#ff8f00</color>
 
diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml
index df84d37d..7fb3d5c0 100644
--- a/app/src/main/res/values/preferences_defaults.xml
+++ b/app/src/main/res/values/preferences_defaults.xml
@@ -4,7 +4,7 @@
     <bool name="pref_default_attendance_present">true</bool>
     <string name="pref_default_grade_average_mode">only_one_semester</string>
     <bool name="pref_default_grade_average_force_calc">false</bool>
-    <bool name="pref_default_expand_grade">false</bool>
+    <string name="pref_default_expand_grade_mode">one</string>
     <bool name="pref_default_grade_statistics_list">false</bool>
     <string name="pref_default_app_theme">light</string>
     <string name="pref_default_grade_color_scheme">vulcan</string>
diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml
index c512a5f2..fef062dd 100644
--- a/app/src/main/res/values/preferences_keys.xml
+++ b/app/src/main/res/values/preferences_keys.xml
@@ -5,7 +5,8 @@
     <string name="pref_key_app_theme">app_theme</string>
     <string name="pref_key_dashboard_tiles">dashboard_tiles</string>
     <string name="pref_key_grade_color_scheme">grade_color_scheme</string>
-    <string name="pref_key_expand_grade">expand_grade</string>
+    <string name="pref_key_expand_grade">expand_grade</string> <!-- replaced by expand_grade_mode -->
+    <string name="pref_key_expand_grade_mode">expand_grade_mode</string>
     <string name="pref_key_grade_average_mode">grade_average_mode</string>
     <string name="pref_key_grade_average_force_calc">grade_average_always_calc</string>
     <string name="pref_key_grade_statistics_list">grade_statistics_list</string>
@@ -34,4 +35,5 @@
     <string name="pref_key_message_send_draft">message_send_recipients</string>
     <string name="pref_key_last_sync_date">last_sync_date</string>
     <string name="pref_key_notifications_piggyback">notifications_piggyback</string>
+    <string name="pref_key_ads_single_support">single_ad_support</string>
 </resources>
diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml
index bd3e8b47..1d777bdb 100644
--- a/app/src/main/res/values/preferences_values.xml
+++ b/app/src/main/res/values/preferences_values.xml
@@ -99,6 +99,17 @@
         <item>grade_color</item>
     </string-array>
 
+    <string-array name="default_expand_grade_entries">
+        <item>Up to 1 at once</item>
+        <item>Always expanded</item>
+        <item>Unlimited expansions</item>
+    </string-array>
+    <string-array name="default_expand_grade_values" translatable="false">
+        <item>one</item>
+        <item>always</item>
+        <item>any</item>
+    </string-array>
+
     <string-array name="grade_average_mode_entries">
         <item>Average of grades only from selected semester</item>
         <item>Average of averages from both semesters</item>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 63d4fc42..6b56e077 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,6 +17,7 @@
     <string name="license_title">Licenses</string>
     <string name="message_title">Messages</string>
     <string name="send_message_title">New message</string>
+    <string name="add_homework_title">New homework</string>
     <string name="note_title">Notes and achievements</string>
     <string name="homework_title">Homework</string>
     <string name="account_title">Accounts manager</string>
@@ -33,7 +34,7 @@
 
     <!--Login-->
     <string name="login_header_default">Sign in with the student or parent account</string>
-    <string name="login_header_symbol">Enter the symbol from the register page</string>
+    <string name="login_header_symbol">Enter the symbol from the register page for account: &lt;b>%1$s&lt;/b></string>
     <string name="login_nickname_hint">Username</string>
     <string name="login_email_hint">Email</string>
     <string name="login_login_pesel_email_hint">Login, PESEL or e-mail</string>
@@ -57,7 +58,6 @@
     <string name="login_invalid_custom_email">Use the assigned login or email in @%1$s</string>
     <string name="login_invalid_symbol">Invalid symbol</string>
     <string name="login_incorrect_symbol">Student not found. Validate the symbol and the chosen variation of the UONET+ register</string>
-    <string name="login_field_required">This field is required</string>
     <string name="login_duplicate_student">Selected student is already logged in</string>
     <string name="login_symbol_helper">The symbol can be found on the register page in&#160;<b>Uczeń</b> →&#160;<b>Dostęp Mobilny</b> →&#160;<b>Zarejestruj urządzenie mobilne</b>.\n\nMake sure that you have set the appropriate register variant in the <b>UONET+ register variant</b> field on the previous screen. Wulkanowy does not detect pre-school students at the moment</string>
     <string name="login_select_student">Select students to log in to the application</string>
@@ -71,7 +71,7 @@
     <string name="login_contact_discord">Discord</string>
     <string name="login_email_intent_title">Send email</string>
     <string name="login_email_subject" translatable="false">Zgłoszenie: Problemy z logowaniem</string>
-    <string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nOpis problemu (pełna nazwa szkoły, klasa ucznia): </string>
+    <string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nPełna nazwa szkoły i klasa ucznia: </string>
     <string name="login_recover_warning">Make sure you select the correct UONET+ register variation!</string>
     <string name="login_recover_button">I forgot my password</string>
     <string name="login_recover_title">Recover your account</string>
@@ -163,6 +163,26 @@
     <string name="timetable_now">Now: %s</string>
     <string name="timetable_next">Next: %s</string>
     <string name="timetable_later">Later: %s</string>
+    <string name="timetable_notify_lesson">%1$s lesson %2$d - %3$s</string>
+    <string name="timetable_notify_change_room">Change of room from %1$s to %2$s</string>
+    <string name="timetable_notify_change_teacher">Change of teacher from %1$s to %2$s</string>
+    <string name="timetable_notify_change_subject">Change of subject from %1$s to %2$s</string>
+    <plurals name="timetable_notify_new_items_title">
+        <item quantity="one">Timetable change</item>
+        <item quantity="other">Timetable changes</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items">
+        <item quantity="one">%1$s - %2$d change in timetable</item>
+        <item quantity="other">%1$s - %2$d  changes in timetable</item>
+    </plurals>
+    <plurals name="timetable_notify_new_items_group">
+        <item quantity="one">%1$d change in timetable</item>
+        <item quantity="other">%1$d changes in timetable</item>
+    </plurals>
+    <plurals name="timetable_number_item">
+        <item quantity="one">%d change</item>
+        <item quantity="other">%d changes</item>
+    </plurals>
 
 
     <!--Completed lessons-->
@@ -200,6 +220,18 @@
     <string name="attendance_excuse_title">Excuse</string>
     <string name="attendance_excuse_reason" translatable="false">z powodu</string>
     <string name="attendance_excuse_formula" translatable="false">Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam.</string>
+    <plurals name="attendance_notify_new_items_title">
+        <item quantity="one">New attendance</item>
+        <item quantity="other">New attendance</item>
+    </plurals>
+    <plurals name="attendance_notify_new_items">
+        <item quantity="one">%1$d new attendance</item>
+        <item quantity="other">%1$d attendance</item>
+    </plurals>
+    <plurals name="attendance_number_item">
+        <item quantity="one">%d attendance</item>
+        <item quantity="other">%d attendance</item>
+    </plurals>
 
 
     <!--Attendance summary-->
@@ -316,6 +348,9 @@
     <string name="homework_no_items">No info about homework</string>
     <string name="homework_mark_as_done">Mark as done</string>
     <string name="homework_mark_as_undone">Mark as undone</string>
+    <string name="homework_add">Add homework</string>
+    <string name="homework_add_success">Homework added successfully</string>
+    <string name="homework_delete_success">Homework deleted successfully</string>
     <string name="homework_attachments">Attachments</string>
     <plurals name="homework_notify_new_item_title">
         <item quantity="one">New homework</item>
@@ -501,6 +536,7 @@
     <!--Dashboard-->
     <string name="dashboard_timetable_title">Lessons</string>
     <string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string>
+    <string name="dashboard_timetable_title_today_and_tomorrow">(Today and tomorrow)</string>
     <string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string>
     <string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
     <string name="dashboard_timetable_first_lesson_title_first">First:</string>
@@ -592,6 +628,10 @@
     <string name="all_no">No</string>
     <string name="all_save">Save</string>
     <string name="all_title">Title</string>
+    <string name="all_add">Add</string>
+    <string name="all_copied">Copied</string>
+    <string name="all_undo">Undo</string>
+    <string name="all_change">Change</string>
 
 
     <!--Timetable Widget-->
@@ -603,13 +643,13 @@
 
 
     <!--Preferences-->
-    <string name="pref_view_header">App appearance &amp; behavior</string>
+    <string name="pref_view_header">App</string>
     <string name="pref_view_list">Default view</string>
     <string name="pref_view_grade_average_mode">Calculated average options</string>
     <string name="pref_view_grade_average_force_calc">Force average calculation by app</string>
     <string name="pref_view_present">Show presence</string>
     <string name="pref_view_app_theme">Theme</string>
-    <string name="pref_view_expand_grade">Expand grades</string>
+    <string name="pref_view_expand_grade">Grades expanding</string>
     <string name="pref_view_timetable_show_timers">Mark current lesson</string>
     <string name="pref_view_timetable_show_groups">Show groups next to subjects</string>
     <string name="pref_view_grade_statistics_list">Show chart list in class grades</string>
@@ -619,6 +659,7 @@
     <string name="pref_view_app_language">Language</string>
 
     <string name="pref_notify_header">Notifications</string>
+    <string name="pref_notify_header_other">Other</string>
     <string name="pref_notify_switch">Show notifications</string>
     <string name="pref_notify_upcoming_lessons_switch">Show upcoming lesson notifications</string>
     <string name="pref_notify_upcoming_lessons_persistent_switch">Make upcoming lesson notification persistent</string>
@@ -650,10 +691,19 @@
     <string name="pref_other_fill_message_content">Reply with message history</string>
     <string name="pref_other_optional_arithmetic_average">Show arithmetic average when no weights provided</string>
 
+    <string name="pref_ads_support_category_name">Support</string>
+    <string name="pref_ads_support">Watch single ad to support project</string>
+    <string name="pref_ads_privacy_title">Consent to data processing</string>
+    <string name="pref_ads_privacy_description">To view an advertisement you must agree to the data processing terms of our Privacy Policy</string>
+    <string name="pref_ads_privacy_agree">Agree</string>
+    <string name="pref_ads_privacy_link">Privacy policy</string>
+    <string name="pref_ads_loading">Ad is loading</string>
+
     <string name="pref_settings_advanced_title">Advanced</string>
     <string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>
     <string name="pref_settings_notifications_title">Notifications</string>
     <string name="pref_settings_sync_title">Synchronization</string>
+    <string name="pref_settings_ads_title">Advertisements</string>
 
     <string name="pref_grades_appearance_header">Grades</string>
     <string name="pref_dashboard_appearance_header">Dashboard</string>
@@ -661,6 +711,7 @@
     <string name="pref_attendance_appearance_view">Attendance</string>
     <string name="pref_timetable_appearance_view">Timetable</string>
     <string name="pref_grades_advanced_header">Grades</string>
+    <string name="pref_counted_average_advanced_header">Calculated average</string>
     <string name="pref_messages_advanced_header">Messages</string>
 
     <string name="pref_appearance_category">Appearance &amp; Behavior</string>
@@ -671,7 +722,8 @@
     <string name="pref_sync_category_summary">Automatic update, synchronization interval</string>
     <string name="pref_advanced_category_summary">Plus and minus values, average calculation</string>
     <string name="pref_advanced_category">Advanced</string>
-    <string name="pref_about_category_summary">App version, contributors, social portals, licenses</string>
+    <string name="pref_about_category_summary">App version, contributors, social portals</string>
+    <string name="pref_ads_category_summary">Displaying advertisements, project support</string>
 
 
     <!--Notification Channels-->
@@ -686,6 +738,8 @@
     <string name="channel_push">Push notifications</string>
     <string name="channel_upcoming_lessons">Upcoming lessons</string>
     <string name="channel_debug">Debug</string>
+    <string name="channel_change_timetable">Timetable change</string>
+    <string name="channel_new_attendance">New attendance</string>
 
 
     <!--Colors-->
@@ -697,12 +751,6 @@
     <string name="all_empty_color">No color</string>
 
 
-    <!--Others-->
-    <string name="all_copied">Copied</string>
-    <string name="all_undo">Undo</string>
-    <string name="all_change">Change</string>
-
-
     <!--Update helper-->
     <string name="update_download_started">Download of updates has started…</string>
     <string name="update_download_success">An update has just been downloaded.</string>
@@ -721,4 +769,5 @@
     <string name="error_unknown">An unexpected error occurred</string>
     <string name="error_feature_disabled">Feature disabled by your school</string>
     <string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string>
+    <string name="error_field_required">This field is required</string>
 </resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 628aa297..7cd0f725 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -11,6 +11,7 @@
         <item name="colorError">@color/colorError</item>
         <item name="colorDivider">@color/colorDivider</item>
         <item name="colorSwipeRefresh">@color/colorSwipeRefresh</item>
+        <item name="colorMessageMedium">@color/dashboard_message_medium_dark</item>
         <item name="android:statusBarColor">@android:color/darker_gray</item>
         <item name="android:textColor">?android:textColorPrimary</item>
         <item name="android:preferenceStyle">@style/PreferenceThemeOverlay</item>
@@ -22,10 +23,11 @@
         <item name="windowActionModeOverlay">true</item>
     </style>
 
-    <style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
-        <item name="android:windowBackground">@drawable/layer_splash_background</item>
-        <item name="android:statusBarColor">@color/colorPrimaryDark</item>
-        <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
+    <style name="WulkanowyTheme.SplashScreen" parent="Theme.SplashScreen">
+        <item name="windowSplashScreenBackground">@color/colorPrimaryDark</item>
+        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash_logo</item>
+        <item name="postSplashScreenTheme">@style/WulkanowyTheme.NoActionBar</item>
+        <item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
     </style>
 
     <style name="WulkanowyTheme.WidgetAccountSwitcher" parent="Theme.MaterialComponents.Light.Dialog">
@@ -37,7 +39,6 @@
     </style>
 
     <style name="Widget.Wulkanowy.Chip.Choice" parent="Widget.MaterialComponents.Chip.Choice">
-        ...
         <item name="materialThemeOverlay">@style/ThemeOverlay.Wulkanowy.Chip.Choice</item>
     </style>
 
@@ -49,8 +50,6 @@
         <item name="android:textSize">11sp</item>
     </style>
 
-    <style name="mdtp_ActionButton.Text" parent="Widget.MaterialComponents.Button.TextButton.Dialog" />
-
     <style name="WulkanowyTheme.Login" parent="WulkanowyTheme.NoActionBar" />
 
     <style name="WulkanowyTheme.MessageSend" parent="WulkanowyTheme.NoActionBar" />
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 00000000..84ff05a0
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+<base-config cleartextTrafficPermitted="true"/>
+  <debug-overrides>
+    <trust-anchors>
+      <certificates src="user" />
+    </trust-anchors>
+  </debug-overrides>
+</network-security-config>
diff --git a/app/src/main/res/xml/scheme_preferences.xml b/app/src/main/res/xml/scheme_preferences.xml
index 08621492..5bf7ad8a 100644
--- a/app/src/main/res/xml/scheme_preferences.xml
+++ b/app/src/main/res/xml/scheme_preferences.xml
@@ -1,33 +1,33 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
     <Preference
-        app:key="appearance"
+        app:fragment="io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment"
         app:icon="@drawable/ic_settings_appearance"
+        app:key="appearance"
         app:summary="@string/pref_appearance_category_summary"
-        app:title="@string/pref_appearance_category"
-        app:fragment="io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment" />
+        app:title="@string/pref_appearance_category" />
     <Preference
-        app:key="notifications"
+        app:fragment="io.github.wulkanowy.ui.modules.settings.notifications.NotificationsFragment"
         app:icon="@drawable/ic_settings_notifications"
+        app:key="notifications"
         app:summary="@string/pref_notifications_category_summary"
-        app:title="@string/pref_notifications_category"
-        app:fragment="io.github.wulkanowy.ui.modules.settings.notifications.NotificationsFragment" />
+        app:title="@string/pref_notifications_category" />
     <Preference
-        app:key="sync"
+        app:fragment="io.github.wulkanowy.ui.modules.settings.sync.SyncFragment"
         app:icon="@drawable/ic_settings_sync"
+        app:key="sync"
         app:summary="@string/pref_sync_category_summary"
-        app:title="@string/pref_sync_category"
-        app:fragment="io.github.wulkanowy.ui.modules.settings.sync.SyncFragment" />
+        app:title="@string/pref_sync_category" />
     <Preference
-        app:key="advanced"
+        app:fragment="io.github.wulkanowy.ui.modules.settings.advanced.AdvancedFragment"
         app:icon="@drawable/ic_settings_advanced"
+        app:key="advanced"
         app:summary="@string/pref_advanced_category_summary"
-        app:title="@string/pref_advanced_category"
-        app:fragment="io.github.wulkanowy.ui.modules.settings.advanced.AdvancedFragment" />
+        app:title="@string/pref_advanced_category" />
     <Preference
-        app:key="about"
+        app:fragment="io.github.wulkanowy.ui.modules.about.AboutFragment"
         app:icon="@drawable/ic_all_about"
+        app:key="about"
         app:summary="@string/pref_about_category_summary"
-        app:title="@string/about_title"
-        app:fragment="io.github.wulkanowy.ui.modules.about.AboutFragment" />
+        app:title="@string/about_title" />
 </PreferenceScreen>
diff --git a/app/src/main/res/xml/scheme_preferences_advanced.xml b/app/src/main/res/xml/scheme_preferences_advanced.xml
index 46103787..95f6f383 100644
--- a/app/src/main/res/xml/scheme_preferences_advanced.xml
+++ b/app/src/main/res/xml/scheme_preferences_advanced.xml
@@ -19,18 +19,16 @@
             app:key="@string/pref_key_grade_modifier_minus"
             app:title="@string/pref_other_grade_modifier_minus"
             app:useSimpleSummaryProvider="true" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        app:title="@string/pref_counted_average_advanced_header">
         <SwitchPreferenceCompat
             app:defaultValue="@bool/pref_default_grade_average_force_calc"
             app:iconSpaceReserved="false"
             app:key="@string/pref_key_grade_average_force_calc"
             app:singleLineTitle="false"
             app:title="@string/pref_view_grade_average_force_calc" />
-        <SwitchPreferenceCompat
-            app:defaultValue="@bool/pref_default_optional_arithmetic_average"
-            app:iconSpaceReserved="false"
-            app:key="@string/pref_key_optional_arithmetic_average"
-            app:singleLineTitle="false"
-            app:title="@string/pref_other_optional_arithmetic_average" />
         <ListPreference
             app:defaultValue="@string/pref_default_grade_average_mode"
             app:entries="@array/grade_average_mode_entries"
@@ -39,6 +37,12 @@
             app:key="@string/pref_key_grade_average_mode"
             app:title="@string/pref_view_grade_average_mode"
             app:useSimpleSummaryProvider="true" />
+        <SwitchPreferenceCompat
+            app:defaultValue="@bool/pref_default_optional_arithmetic_average"
+            app:iconSpaceReserved="false"
+            app:key="@string/pref_key_optional_arithmetic_average"
+            app:singleLineTitle="false"
+            app:title="@string/pref_other_optional_arithmetic_average" />
     </PreferenceCategory>
     <PreferenceCategory
         app:iconSpaceReserved="false"
diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml
index b34fd417..b2da0287 100644
--- a/app/src/main/res/xml/scheme_preferences_appearance.xml
+++ b/app/src/main/res/xml/scheme_preferences_appearance.xml
@@ -50,11 +50,14 @@
             app:iconSpaceReserved="false"
             app:key="@string/pref_key_grade_color_scheme"
             app:title="@string/pref_view_grade_color_scheme" />
-        <SwitchPreferenceCompat
-            app:defaultValue="@bool/pref_default_expand_grade"
+        <ListPreference
+            app:defaultValue="@string/pref_default_expand_grade_mode"
+            app:entries="@array/default_expand_grade_entries"
+            app:entryValues="@array/default_expand_grade_values"
             app:iconSpaceReserved="false"
-            app:key="@string/pref_key_expand_grade"
-            app:title="@string/pref_view_expand_grade" />
+            app:key="@string/pref_key_expand_grade_mode"
+            app:title="@string/pref_view_expand_grade"
+            app:useSimpleSummaryProvider="true" />
         <SwitchPreferenceCompat
             app:defaultValue="@bool/pref_default_subjects_without_grades"
             app:iconSpaceReserved="false"
diff --git a/app/src/main/res/xml/scheme_preferences_notifications.xml b/app/src/main/res/xml/scheme_preferences_notifications.xml
index 78e91cf0..442581bf 100644
--- a/app/src/main/res/xml/scheme_preferences_notifications.xml
+++ b/app/src/main/res/xml/scheme_preferences_notifications.xml
@@ -22,18 +22,22 @@
             app:singleLineTitle="false"
             app:summary="@string/pref_notify_upcoming_lessons_persistent_summary"
             app:title="@string/pref_notify_upcoming_lessons_persistent_switch" />
-        <SwitchPreferenceCompat
-            app:defaultValue="@bool/pref_default_notification_piggyback"
-            app:iconSpaceReserved="false"
-            app:key="@string/pref_key_notifications_piggyback"
-            app:singleLineTitle="false"
-            app:title="@string/pref_notify_notifications_piggyback" />
         <SwitchPreferenceCompat
             app:defaultValue="@bool/pref_default_notification_debug"
             app:iconSpaceReserved="false"
             app:key="@string/pref_key_notification_debug"
             app:singleLineTitle="false"
             app:title="@string/pref_notify_debug_switch" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        app:title="@string/pref_notify_header_other">
+        <SwitchPreferenceCompat
+            app:defaultValue="@bool/pref_default_notification_piggyback"
+            app:iconSpaceReserved="false"
+            app:key="@string/pref_key_notifications_piggyback"
+            app:singleLineTitle="false"
+            app:title="@string/pref_notify_notifications_piggyback" />
         <Preference
             app:iconSpaceReserved="false"
             app:key="@string/pref_key_notifications_system_settings"
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
new file mode 100644
index 00000000..960a54b8
--- /dev/null
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt
@@ -0,0 +1,94 @@
+package io.github.wulkanowy.ui.modules.settings.ads
+
+import android.os.Bundle
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.wulkanowy.R
+import io.github.wulkanowy.ui.base.BaseActivity
+import io.github.wulkanowy.ui.base.ErrorDialog
+import io.github.wulkanowy.ui.modules.main.MainView
+import io.github.wulkanowy.utils.openInternetBrowser
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
+
+    @Inject
+    lateinit var presenter: AdsPresenter
+
+    override val titleStringId = R.string.pref_settings_ads_title
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        setPreferencesFromResource(R.xml.scheme_preferences_ads, rootKey)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        presenter.onAttachView(this)
+    }
+
+    override fun initView() {
+        findPreference<Preference>(getString(R.string.pref_key_ads_single_support))?.setOnPreferenceClickListener {
+            presenter.onWatchSingleAdSelected()
+            true
+        }
+    }
+
+    override fun showAd(ad: RewardedInterstitialAd) {
+        if (isVisible) {
+            ad.show(requireActivity()) {}
+        }
+    }
+
+    override fun showPrivacyPolicyDialog() {
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(getString(R.string.pref_ads_privacy_title))
+            .setMessage(getString(R.string.pref_ads_privacy_description))
+            .setPositiveButton(getString(R.string.pref_ads_privacy_agree)) { _, _ -> presenter.onAgreedPrivacy() }
+            .setNegativeButton(android.R.string.cancel) { _, _ -> }
+            .setNeutralButton(getString(R.string.pref_ads_privacy_link)) { _, _ -> presenter.onPrivacySelected() }
+            .show()
+    }
+
+    override fun openPrivacyPolicy() {
+        requireContext().openInternetBrowser(
+            "https://wulkanowy.github.io/polityka-prywatnosci.html",
+            ::showMessage
+        )
+    }
+
+    override fun showLoadingSupportAd(show: Boolean) {
+        findPreference<Preference>(getString(R.string.pref_key_ads_single_support))?.run {
+            isEnabled = !show
+            summary = if (show) getString(R.string.pref_ads_loading) else null
+        }
+    }
+
+    override fun showError(text: String, error: Throwable) {
+        (activity as? BaseActivity<*, *>)?.showError(text, error)
+    }
+
+    override fun showMessage(text: String) {
+        (activity as? BaseActivity<*, *>)?.showMessage(text)
+    }
+
+    override fun showExpiredDialog() {
+        (activity as? BaseActivity<*, *>)?.showExpiredDialog()
+    }
+
+    override fun showChangePasswordSnackbar(redirectUrl: String) {
+        (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl)
+    }
+
+    override fun openClearLoginView() {
+        (activity as? BaseActivity<*, *>)?.openClearLoginView()
+    }
+
+    override fun showErrorDetailsDialog(error: Throwable) {
+        ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
+    }
+}
\ No newline at end of file
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt
new file mode 100644
index 00000000..fd5cc9b6
--- /dev/null
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt
@@ -0,0 +1,41 @@
+package io.github.wulkanowy.ui.modules.settings.ads
+
+import io.github.wulkanowy.data.repositories.StudentRepository
+import io.github.wulkanowy.ui.base.BasePresenter
+import io.github.wulkanowy.ui.base.ErrorHandler
+import io.github.wulkanowy.utils.AdsHelper
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+class AdsPresenter @Inject constructor(
+    errorHandler: ErrorHandler,
+    studentRepository: StudentRepository,
+    private val adsHelper: AdsHelper
+) : BasePresenter<AdsView>(errorHandler, studentRepository) {
+
+    override fun onAttachView(view: AdsView) {
+        super.onAttachView(view)
+        view.initView()
+        Timber.i("Settings ads view was initialized")
+    }
+
+    fun onWatchSingleAdSelected() {
+        view?.showPrivacyPolicyDialog()
+    }
+
+    fun onPrivacySelected() {
+        view?.openPrivacyPolicy()
+    }
+
+    fun onAgreedPrivacy() {
+        view?.showLoadingSupportAd(true)
+        presenterScope.launch {
+            runCatching { adsHelper.getSupportAd() }
+                .onFailure(errorHandler::dispatch)
+                .onSuccess { it?.let { view?.showAd(it) } }
+
+            view?.showLoadingSupportAd(false)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt
new file mode 100644
index 00000000..25eeaaec
--- /dev/null
+++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt
@@ -0,0 +1,17 @@
+package io.github.wulkanowy.ui.modules.settings.ads
+
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
+import io.github.wulkanowy.ui.base.BaseView
+
+interface AdsView : BaseView {
+
+    fun initView()
+
+    fun showAd(ad: RewardedInterstitialAd)
+
+    fun showPrivacyPolicyDialog()
+
+    fun openPrivacyPolicy()
+
+    fun showLoadingSupportAd(show: Boolean)
+}
\ No newline at end of file
diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
new file mode 100644
index 00000000..f363c13f
--- /dev/null
+++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt
@@ -0,0 +1,39 @@
+package io.github.wulkanowy.utils
+
+import android.content.Context
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.MobileAds
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.wulkanowy.BuildConfig
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+class AdsHelper @Inject constructor(@ApplicationContext private val context: Context) {
+
+    suspend fun getSupportAd(): RewardedInterstitialAd? {
+        MobileAds.initialize(context)
+
+        val adRequest = AdRequest.Builder().build()
+
+        return suspendCoroutine {
+            RewardedInterstitialAd.load(
+                context,
+                BuildConfig.SINGLE_SUPPORT_AD_ID,
+                adRequest,
+                object : RewardedInterstitialAdLoadCallback() {
+                    override fun onAdLoaded(rewardedInterstitialAd: RewardedInterstitialAd) {
+                        it.resume(rewardedInterstitialAd)
+                    }
+
+                    override fun onAdFailedToLoad(loadAdError: LoadAdError) {
+                        it.resumeWithException(IllegalArgumentException(loadAdError.message))
+                    }
+                })
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
index f8b80574..410fddf1 100644
--- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
+++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt
@@ -4,12 +4,6 @@ import android.util.Log
 import com.google.firebase.crashlytics.FirebaseCrashlytics
 import fr.bipi.tressence.base.FormatterPriorityTree
 import fr.bipi.tressence.common.StackTraceRecorder
-import fr.bipi.tressence.common.filters.Filter
-import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
-import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
-import java.io.InterruptedIOException
-import java.net.SocketTimeoutException
-import java.net.UnknownHostException
 
 class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
 
diff --git a/app/src/play/res/xml/scheme_preferences.xml b/app/src/play/res/xml/scheme_preferences.xml
new file mode 100644
index 00000000..05b0bf64
--- /dev/null
+++ b/app/src/play/res/xml/scheme_preferences.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment"
+        app:icon="@drawable/ic_settings_appearance"
+        app:key="appearance"
+        app:summary="@string/pref_appearance_category_summary"
+        app:title="@string/pref_appearance_category" />
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.settings.notifications.NotificationsFragment"
+        app:icon="@drawable/ic_settings_notifications"
+        app:key="notifications"
+        app:summary="@string/pref_notifications_category_summary"
+        app:title="@string/pref_notifications_category" />
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.settings.sync.SyncFragment"
+        app:icon="@drawable/ic_settings_sync"
+        app:key="sync"
+        app:summary="@string/pref_sync_category_summary"
+        app:title="@string/pref_sync_category" />
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.settings.ads.AdsFragment"
+        app:icon="@drawable/ic_settings_ads"
+        app:key="ads"
+        app:summary="@string/pref_ads_category_summary"
+        app:title="@string/pref_settings_ads_title" />
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.settings.advanced.AdvancedFragment"
+        app:icon="@drawable/ic_settings_advanced"
+        app:key="advanced"
+        app:summary="@string/pref_advanced_category_summary"
+        app:title="@string/pref_advanced_category" />
+    <Preference
+        app:fragment="io.github.wulkanowy.ui.modules.about.AboutFragment"
+        app:icon="@drawable/ic_all_about"
+        app:key="about"
+        app:summary="@string/pref_about_category_summary"
+        app:title="@string/about_title" />
+</PreferenceScreen>
diff --git a/app/src/play/res/xml/scheme_preferences_ads.xml b/app/src/play/res/xml/scheme_preferences_ads.xml
new file mode 100644
index 00000000..6b3625ca
--- /dev/null
+++ b/app/src/play/res/xml/scheme_preferences_ads.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        app:title="@string/pref_ads_support_category_name">
+        <Preference
+            app:iconSpaceReserved="false"
+            app:key="@string/pref_key_ads_single_support"
+            app:singleLineTitle="true"
+            app:title="@string/pref_ads_support" />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt b/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt
index e60b1d7a..d4cba7b5 100644
--- a/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt
+++ b/app/src/test/java/io/github/wulkanowy/TestDispatchersProvider.kt
@@ -1,11 +1,9 @@
 package io.github.wulkanowy
 
 import io.github.wulkanowy.utils.DispatchersProvider
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 
 class TestDispatchersProvider : DispatchersProvider() {
 
-    override val backgroundThread: CoroutineDispatcher
-        get() = Dispatchers.Unconfined
+    override val io get() = Dispatchers.Unconfined
 }
diff --git a/app/src/test/java/io/github/wulkanowy/data/db/ConvertersTest.kt b/app/src/test/java/io/github/wulkanowy/data/db/ConvertersTest.kt
index 3b0d7c84..a044a429 100644
--- a/app/src/test/java/io/github/wulkanowy/data/db/ConvertersTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/db/ConvertersTest.kt
@@ -23,8 +23,8 @@ class ConvertersTest {
 
     @Test
     fun jsonToStringPairList_0210() {
-        assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\",\"ccc\":\"ddd\"}"), listOf("aaa" to "bbb", "ccc" to "ddd"))
-        assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\"}"), listOf("aaa" to "bbb"))
+        assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\",\"ccc\":\"ddd\"}"), listOf<Pair<String, String>>())
+        assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\"}"), listOf<Pair<String, String>>())
         assertEquals(Converters().jsonToStringPairList("{}"), listOf<Pair<String, String>>())
     }
 }
diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt
index f9fc7631..cc31d893 100644
--- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/AbstractMigrationTest.kt
@@ -16,6 +16,8 @@ abstract class AbstractMigrationTest {
 
     val dbName = "migration-test"
 
+    val context: Context get() = ApplicationProvider.getApplicationContext()
+
     @get:Rule
     val helper: MigrationTestHelper = MigrationTestHelper(
         InstrumentationRegistry.getInstrumentation(),
@@ -24,7 +26,6 @@ abstract class AbstractMigrationTest {
     )
 
     fun getMigratedRoomDatabase(): AppDatabase {
-        val context = ApplicationProvider.getApplicationContext<Context>()
         val database = Room.databaseBuilder(
             ApplicationProvider.getApplicationContext(),
             AppDatabase::class.java,
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
index 25774d74..052f08f0 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt
@@ -1,7 +1,6 @@
 package io.github.wulkanowy.data.repositories
 
 import android.content.Context
-import com.squareup.moshi.Moshi
 import io.github.wulkanowy.data.Status
 import io.github.wulkanowy.data.db.SharedPrefProvider
 import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
@@ -30,6 +29,7 @@ import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -63,9 +63,6 @@ class MessageRepositoryTest {
 
     private lateinit var repository: MessageRepository
 
-    @MockK
-    private lateinit var moshi: Moshi
-
     @Before
     fun setUp() {
         MockKAnnotations.init(this)
@@ -78,7 +75,7 @@ class MessageRepositoryTest {
             context = context,
             refreshHelper = refreshHelper,
             sharedPrefProvider = sharedPrefProvider,
-            moshi = moshi,
+            json = Json,
         )
     }
 
diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
index b34bbf1b..9d3d7a2e 100644
--- a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt
@@ -32,13 +32,13 @@ class StudentTest {
     fun initApi() {
         MockKAnnotations.init(this)
         studentRepository = StudentRepository(
-            mockk(),
-            TestDispatchersProvider(),
-            studentDb,
-            semesterDb,
-            mockSdk,
-            AppInfo(),
-            mockk()
+            context = mockk(),
+            dispatchers = TestDispatchersProvider(),
+            studentDb = studentDb,
+            semesterDb = semesterDb,
+            sdk = mockSdk,
+            appInfo = AppInfo(),
+            appDatabase = mockk()
         )
     }
 
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
index 2c44e9fa..7557d745 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt
@@ -42,20 +42,12 @@ class MainPresenterTest {
         MockKAnnotations.init(this)
         clearMocks(mainView)
 
-        every { mainView.startMenuIndex = any() } just Runs
-        every { mainView.startMenuMoreIndex = any() } just Runs
-        every { mainView.startMenuIndex } returns 1
-        every { mainView.startMenuMoreIndex } returns 1
-        every { mainView.initView() } just Runs
-        presenter = MainPresenter(errorHandler, studentRepository, prefRepository, syncManager, analytics)
+        every { mainView.initView(any(), any()) } just Runs
+        presenter =
+            MainPresenter(errorHandler, studentRepository, prefRepository, syncManager, analytics)
         presenter.onAttachView(mainView, null)
     }
 
-    @Test
-    fun initMenuTest() {
-        verify { mainView.initView() }
-    }
-
     @Test
     fun onTabSelectedTest() {
         every { mainView.notifyMenuViewChanged() } just Runs
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt
index 98570144..32311974 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt
@@ -37,7 +37,7 @@ class SplashPresenterTest {
     fun testOpenLoginView() {
         coEvery { studentRepository.isCurrentStudentSet() } returns false
 
-        presenter.onAttachView(splashView, null)
+        presenter.onAttachView(splashView, null, null)
         verify { splashView.openLoginView() }
     }
 
@@ -45,7 +45,7 @@ class SplashPresenterTest {
     fun testMainMainView() {
         coEvery { studentRepository.isCurrentStudentSet() } returns true
 
-        presenter.onAttachView(splashView, null)
-        verify { splashView.openMainView() }
+        presenter.onAttachView(splashView, null, null)
+        verify { splashView.openMainView(null) }
     }
 }
diff --git a/build.gradle b/build.gradle
index 15d3c792..2c6d9218 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
 buildscript {
     ext {
         kotlin_version = '1.5.31'
-        about_libraries = '8.9.1'
-        hilt_version = "2.38.1"
+        about_libraries = '8.9.4'
+        hilt_version = "2.40.1"
     }
     repositories {
         mavenCentral()
@@ -12,11 +12,12 @@ buildscript {
     }
     dependencies {
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        classpath 'com.android.tools.build:gradle:7.0.2'
+        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
+        classpath 'com.android.tools.build:gradle:7.0.3'
         classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
         classpath 'com.google.gms:google-services:4.3.10'
-        classpath 'com.huawei.agconnect:agcp:1.6.0.300'
-        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
+        classpath 'com.huawei.agconnect:agcp:1.6.1.300'
+        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0'
         classpath "com.github.triplet.gradle:play-publisher:3.6.0"
         classpath "ru.cian:huawei-publish-gradle-plugin:1.3.0"
         classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3"