From 54fbd56b7382f956dc745addd4e91d3cf23dc17a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 04:33:56 +0000 Subject: [PATCH 01/74] Bump com.google.firebase:firebase-bom from 31.5.0 to 32.0.0 (#2190) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index de0c2a3c..0f6789e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.5.0') + playImplementation platform('com.google.firebase:firebase-bom:32.0.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From cb8303f33dc1953db0dcf31516ca36f396aede29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 04:34:14 +0000 Subject: [PATCH 02/74] Bump com.google.android.material:material from 1.8.0 to 1.9.0 (#2191) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0f6789e8..85ac4833 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,7 +206,7 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" - implementation "com.google.android.material:material:1.8.0" + implementation "com.google.android.material:material:1.9.0" implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation 'com.github.lopspower:CircularImageView:4.3.0' From a0af55825d3c8e0a39ca559b6de804f76ee3d309 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 04:34:32 +0000 Subject: [PATCH 03/74] Bump about_libraries from 10.6.2 to 10.6.3 (#2189) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3c8552d2..c63a8fd0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.8.21' - about_libraries = '10.6.2' + about_libraries = '10.6.3' hilt_version = "2.45" } repositories { From c33b309cf068efd51566fb52048da140f9046208 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 04:34:48 +0000 Subject: [PATCH 04/74] Bump org.robolectric:robolectric from 4.10 to 4.10.2 (#2188) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 85ac4833..4ea086a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -265,7 +265,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.10' + testImplementation 'org.robolectric:robolectric:4.10.2' testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" From 030fe8c218a2508789c5750d9d3412334aa1fb03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 04:45:15 +0000 Subject: [PATCH 05/74] Bump coroutines from 1.6.4 to 1.7.0 (#2186) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4ea086a1..3a6744c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -182,7 +182,7 @@ ext { room = "2.5.1" chucker = "3.5.2" mockk = "1.13.5" - coroutines = "1.6.4" + coroutines = "1.7.0" } dependencies { From f2faa7e8b7f27c7f7f182117fc8b83d02e0e8122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 12 May 2023 22:45:24 +0200 Subject: [PATCH 06/74] Fix button color in high priority admin message (#2202) --- .../ui/modules/dashboard/adapters/DashboardAdapter.kt | 5 +++-- app/src/main/res/values-night-v31/styles.xml | 4 +++- app/src/main/res/values-night/styles.xml | 4 +++- app/src/main/res/values-v31/styles.xml | 4 +++- app/src/main/res/values/attrs.xml | 2 ++ app/src/main/res/values/colors.xml | 6 ++++-- app/src/main/res/values/styles.xml | 4 +++- 7 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt index 2c06e45f..4ad4e9d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt @@ -738,8 +738,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter { - context.getThemeAttrColor(R.attr.colorPrimary) to - context.getThemeAttrColor(R.attr.colorOnPrimary) + context.getThemeAttrColor(R.attr.colorMessageHigh) to + context.getThemeAttrColor(R.attr.colorOnMessageHigh) } "MEDIUM" -> { context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK @@ -754,6 +754,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter@color/colorErrorLight @color/colorDividerInverse @color/material_dynamic_secondary20 - @color/dashboard_message_medium_light + @color/dashboard_message_medium_dark + @color/dashboard_message_high_dark + @android:color/black ?colorSurface @color/material_dynamic_neutral90 @android:color/transparent diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 7d2f0cfe..5d9aa22a 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -24,7 +24,9 @@ @color/colorErrorLight @color/colorDividerInverse @color/colorSwipeRefreshDark - @color/dashboard_message_medium_light + @color/dashboard_message_medium_dark + @color/dashboard_message_high_dark + @android:color/black ?colorSurface ?android:textColorPrimary @color/colorNavigationBarDark diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml index cffb284e..bb47b22e 100644 --- a/app/src/main/res/values-v31/styles.xml +++ b/app/src/main/res/values-v31/styles.xml @@ -37,7 +37,9 @@ @color/colorError @color/colorDivider @color/material_dynamic_secondary90 - @color/dashboard_message_medium_dark + @color/dashboard_message_medium_light + @color/dashboard_message_high_light + @android:color/white @color/material_dynamic_neutral10 @android:color/transparent @android:color/transparent diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 8986f357..aa58fa09 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -5,4 +5,6 @@ + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ac1b1c19..87057c61 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -39,8 +39,10 @@ #342826 #181010 - #FFD980 - #ffd54f + #ffd54f + #FFD980 + #B91B21 + #e57373 #d32f2f #e57373 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9d1f0745..603e22ab 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -22,7 +22,9 @@ @color/colorError @color/colorDivider @color/colorSwipeRefresh - @color/dashboard_message_medium_dark + @color/dashboard_message_medium_light + @color/dashboard_message_high_light + @android:color/white ?android:textColorPrimary @android:color/black @style/PreferenceThemeOverlay From cc752ab0ad36510bff1df10bfee1eda641768ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 12 May 2023 22:45:50 +0200 Subject: [PATCH 07/74] New Crowdin updates (#2201) --- app/src/main/res/values-cs/strings.xml | 14 +++++----- app/src/main/res/values-de/strings.xml | 36 +++++++++++++------------- app/src/main/res/values-sk/strings.xml | 14 +++++----- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index beb2996b..f8c19dff 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -810,14 +810,14 @@ Pro uložení změn je nutné aplikaci restartovat Restartovat - Authorization has been rejected. The data provided does not match the records in the secretary\'s office. - Invalid PESEL + Autorizace byla zamítnuta. Uvedené údaje se neshodují se záznamy v kanceláři tajemníka. + Neplatný PESEL PESEL - Authorize - Authorization completed successfully - Authorization - To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below - Skip for now + Autorizovat + Autorizace byla úspěšně dokončena + Autorizace + Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli + Zatím přeskočit Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4fdd71b5..500553e2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -26,7 +26,7 @@ Schülerinfo Übersicht Benachrichtigungszentrum - Menu configuartion + Menü Konfiguration Semester %1$d, %2$d/%3$d @@ -56,7 +56,7 @@ Ungültige symbol Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers Ausgewählter Student ist bereits angemeldet. - The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen + Das Symbol kann auf der Registerseite in Student → Tost Möbeln → Registrieren Sie Ihr Mobilgerätgefunden werden.\n\nStellen Sie sicher, dass Sie die entsprechende Registervariante im Feld UONET+ Registervariante auf dem vorherigen Bildschirm festgelegt haben Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen Andere Optionen In diesem Modus funktioniert eine Glücknummer, eine Klassenstatistik, eine Zusammenfassung der Anwesenheit, eine Entschuldigung für die Abwesenheit, abgeschlossene Lektionen, Schulinformationen und eine Vorschau der Liste der registrierten Geräte nicht @@ -73,14 +73,14 @@ Wiederherstellen Student ist bereits angemeldet Standard - Other search locations - No active students found - Enter a different symbol + Andere Suchorte + Keine aktiven Schüler gefunden + Geben Sie ein anderes Symbol ein - Enable notifications - Enable notifications so you don\'t miss message from teacher or new grade - Skip - Enable + Benachrichtigungen aktivieren + Aktivieren Sie Benachrichtigungen, damit Sie keine Nachricht vom Lehrer oder eine neue Klasse verpassen + Überspringen + Ermöglichen Kundenbetreuer Anmelden @@ -288,7 +288,7 @@ Nur ungelesen Nur mit Anhängen Lesen: %s - Read by: %1$d of %2$d people + Lesen von: %1$d von %2$d Personen %1$d Nachricht %1$d Nachrichten @@ -422,8 +422,8 @@ Teilnahme an einem Meeting Agenda - Place - Topic + Ort + Thema Schulankündigungen Keine schulankündigungen @@ -591,10 +591,10 @@ lösen Ändern Zum Kalender hinzufügen - Cancel + Stornieren Keine Lektionen - Synchronized on %1$s at %2$s + Synchronisiert am %1$s am %2$s Thema wählen Licht Dunkel @@ -614,8 +614,8 @@ Farbschema der Noten Schulfachen sortieren Sprache - Menu configuration - Set the order of functions in the menu + Menü Konfiguration + Legen Sie die Reihenfolge der Funktionen im Menü fest Benachrichtigungen Sonstiges Benachrichtigungen anzeigen @@ -718,8 +718,8 @@ Neustart Update fehlgeschlagen! Wulkanowy funktioniert möglicherweise nicht richtig. Überlegen Sie die Aktualisierung - Application restart - The application must restart for the changes to be saved + Neustart der Anwendung + Die Anwendung muss neu gestartet werden, damit die Änderungen gespeichert werden Restart Authorization has been rejected. The data provided does not match the records in the secretary\'s office. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 7e42c6cd..950eb01e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -810,14 +810,14 @@ Pre uloženie zmien je nutné aplikáciu reštartovať Reštartovať - Authorization has been rejected. The data provided does not match the records in the secretary\'s office. - Invalid PESEL + Autorizácia bola zamietnutá. Uvedené údaje sa nezhodujú so záznamami v kancelárii tajomníka. + Neplatný PESEL PESEL - Authorize - Authorization completed successfully - Authorization - To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below - Skip for now + Autorizovať + Autorizácia bola úspešne dokončená + Autorizácia + Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli + Zatiaľ preskočiť Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia From 1e9a6a5c42bc11d80bb9f73fcb788f5a0b051cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 12 May 2023 22:59:40 +0200 Subject: [PATCH 08/74] Version 2.0.3 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3a6744c1..a4f230b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 124 - versionName "2.0.2" + versionCode 125 + versionName "2.0.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,8 +161,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS - userFraction = 0.10d - updatePriority = 2 + userFraction = 0.50d + updatePriority = 3 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.1' + implementation 'io.github.wulkanowy:sdk:2.0.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 378dedce..aee30290 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 2.0.2 +Wersja 2.0.3 — zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3 — dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym From adf418cc689c479ac898a14bac0d7a4219d4620f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 13 May 2023 10:44:09 +0200 Subject: [PATCH 09/74] Fix delete user homework button visibility (#2204) --- app/src/main/res/layout/item_homework_dialog_details.xml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 1b1c1d39..a40b5dce 100644 --- a/app/src/main/res/layout/item_homework_dialog_details.xml +++ b/app/src/main/res/layout/item_homework_dialog_details.xml @@ -14,16 +14,13 @@ + android:textColor="?attr/colorOnSurface" /> Date: Sun, 14 May 2023 17:28:29 +0000 Subject: [PATCH 10/74] Bump hilt_version from 2.45 to 2.46.1 (#2205) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c63a8fd0..7161e4c3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.8.21' about_libraries = '10.6.3' - hilt_version = "2.45" + hilt_version = "2.46.1" } repositories { mavenCentral() From a06add070ecc9543a9a5235bf822b89d4d6775fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 18:20:56 +0000 Subject: [PATCH 11/74] Bump com.android.tools.build:gradle from 7.4.2 to 8.0.1 (#2187) --- .github/workflows/deploy-store.yml | 4 ++-- .github/workflows/deploy-test.yml | 4 ++-- .github/workflows/test.yml | 6 +++--- app/build.gradle | 6 +++++- build.gradle | 2 +- gradle.properties | 9 +++++++-- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- 9 files changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-store.yml b/.github/workflows/deploy-store.yml index 3ce618ca..e8a220dd 100644 --- a/.github/workflows/deploy-store.yml +++ b/.github/workflows/deploy-store.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | @@ -52,7 +52,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index 20082590..f2e9f016 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | @@ -92,7 +92,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f8591bb..bc4b3647 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | @@ -48,7 +48,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | @@ -74,7 +74,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - uses: actions/cache@v3 with: path: | diff --git a/app/build.gradle b/app/build.gradle index a4f230b8..ad83461a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,7 +156,11 @@ android { kapt { correctErrorTypes true } - +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} play { defaultToAppBundles = false track = 'production' diff --git a/build.gradle b/build.gradle index 7161e4c3..88079e54 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.0.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.9.0.300' diff --git a/gradle.properties b/gradle.properties index 38603830..5a8099a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,13 +11,18 @@ org.gradle.jvmargs=-Xmx1536m # android.enableJetifier=true android.useAndroidX=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false # kotlin.code.style=official -# kapt.use.worker.api=true kapt.include.compile.classpath=false # +# https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-common-faq-0000001063210244#section17273113244910 +apmsInstrumentationEnabled=false +# # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 5094 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 delta 5107 zcmZ4Skh$$4Gi!i1GYi*bMjp|LQo@W}6ZHxi!90Ez1_p-wqI7*`28as9dy*6H3PL0& zvoOjsa!sCnPja(0V<;CR*W`)Z`CuszFey5D!+n;?$vmQrT$?L-x|kTbHnZ?=U}5B% zyx@W4=5xX|jEr29|38r2oFeMR$jG&MwU{d#BiH1QGOrl9COwke{8hG#nUQOAgW@NU z)?$?~7Dlekr!>|viEuG6aBwg%FgP+qFRm89%*Mc=COG-Msruv(k7er5M!YN+4i)*o z*X-<)%Z%O=wOk8bUQ8-!Y_bl^c3rW>b*-4T9?!`Md`pZ=(=Ijr-lxB!;~H-OOJGPq z^qL)W7t5L3{1ct``2K^3E54uexun{-rRLM=>il=RfB%{k|NrMFX@;{uLfmxorU$lL ztK89CEFta9x#;7j&gmBVoN*;?ck6W&B^~FpI3H8WnHbo%w#Li#LOh$Wm@@B)6~|)# zWO^5*h9ns@DM}jVOx`G4{`st}QQm>QJr(gR-;JL%8r|t&udMVqbhzd(N4$Mh+wl(# zEyrhm;pFu9s_=5y$QEeIco;M$b3-jLw-hdWBbZ{@1^p7p(b zq~hb0*HizMdl^smZM>^{CU0q?R9^RieYYg5*4A%W&SjmJcI^6Bj+R?%SR%{W7MCpj zZT|9`@2zDbj#^f0c1(M9F{gPybHa(Y#wS9jX8UHJ__B4@lfT~F+7sqau~CmsTRqq3 z`|D-e1*N&`s$Zu`-RhY3G<@P|zXUPGW82HJm>WwsTZ+wkTlVsHOpt3r=G&gsbAGSh zNu8D4zGTO?clBZCEf&dmynM4%NdKuuvF>*N?blhCF5;vrMasAlHqna!6^yj<|0 zXw8$<_5*i5zRKI0n`=GYB7X0R2T3uD6uR74eIr&SNX`<~Iqu4}uKsXTVN)pQe$765Ep0h`j1%Q+f9D%fvow-+goh8zCDs< zeV;VFskXxZU|pY!ce>S6sgUHzYkgKrZvWacQ~tnjF1}4)ilaG-Etil)Tx>32H5TmbIq;TbLv*?-D~wR zrtCRJZ@&u{a&+nIb#uR71U?%+v`%B&H3!qxOo@fg-N`3wM0i za8lOjJmx0Qcdg%{%dAI1Xs-W(OUiQnVo~BwOh=+M1oIcf?CGscGGBE>ZjSo5q|OTV zaG`Tt^NrUXy4p~lV!L9KZ2En^#c55e_9>q~_u4~D{NJI?Jxjt$ zf244~ur=)}-gEfag?&BT=gzXECjMK{Gx7SxNPD-k$u}xXEaD$n>%8hR?RnHx&;0-2 z)6J1KZkDEf;AZX>CcJ-BggPwronygvA?wwf$VUSipT5OV0G= z4&D7zeJv%;*+0=eImK}tLbY{1^Li|A5>j_ zwEhFCOyAl51PQ)ycn?xm|4ednim24&1J5KjE4y%lgzi6+-0bG2z|6=s+2^_B<_6DB zAyCcyTykc6;FPH~9$%*;M$*1;M&(LqlqsI>8h z&l|;=rzG7UbN&&LHdEO6fcb~Uk&VKf9I+*a#q!1HuI~JP?tJ|HdWMRwy$qjsZ0^|2 z`0nApeqVY18WUErd6|pv7B@z%uGM=W`+TL)4$17r_j=lGQ_rX9CodCx-g)NS(yOU9 zzEyb(xEt#~Of@_um%d3u&x<6?=_6Ch9$g=*O)3{xhCYFyAa2& zpEuN2xp3vgs6APcvhlU>`eCEShI+fqmj;uQC-Y3PwYgRA zzwGNq*(CPeOmDT#^#cAc{*wFNNp;SFTlamZ@?I3u-B5mXSI)y`vrHS&A5lh|Esd{z zd-LMeW@X_I?Uyz(l<&N?D~C7Z(Bt|?pMLCoR$(FUcKwCxmZq}0|I$aMzp{L=H$auY zU~ahu;|0BoLB_9wKXG2%>bB{QWAZ!gtoi9L0$ua{6oRW>-gR1B^?hT{)Ky2aj>a6e zTHAj6#sS_c#=y9v2lK({4BpVnWYk$L&&t4{!Zq1(x#VPrLYey8!NTDZ|7DABZqVs4 z)aq>7=;X;DtiLsELs+lI1y)_g+%32CPAaa+D|?x?_(InEwR>&8H*WvO`AzkP(bmh; z%a+woe*DFHx2I>)o&#rYZodEZd#w3?yT5-88y5fYbYl=UiaX58DegRDih*jf)caE( z9H$y6Cx*H4Oizh9R&DfbLcQqmJv{nDE)-;>v_mmj#$2`L zMrT}&iX(So;Ehw=ixv3v5vDDi)d~*JDq1<`6ao2m2z|SyxgVp_IRwm z8GLxT=f3WusrzjguicR(R8nNNan^I~zPy>KoZDAedGCr0*xEHS{@FI^`1(bvrE5ehGs6m$Fipmg;^AfUcTu4 zr~U?yaJ}T}B#}qUo*v;n)qeQ#Iivpg5^a(7$=&|*=XSqNn;UcCY9Bv5#M0h2>v-)_L!$TlkvBhGg9{z7y}eY%0uHer)z@ ztI)|I^CyP1ew=pdinVfm&=bG1wQoKIeXjCQiB>Pkww-V)_3SgXqqEoKZ`!r|c}Ay& z)F-q3ImNv~cc!ktaVX%g?WHHz7IS?_eV1}eXJf=8)^}DsyRL7$k+A#9wwpOhTdF>u z-?L=OJikw_uk}~VE?;wd&r;ni+Yghrt(drCMWvxW4}0{rnBQrfpF(fRq<)`VU)H-Y zSl?V{8((vUQ)JEdS!ZWPdFt4Foa?pijl0YO{tAXi=h@Y!$<1AVIeON-o_UQAc{jwr z+{Ei#!WNq(!f7Sy&)~^(d2%AlVing@Z&*}*vA^rLJGAYl^Zy0q!uw)wy#LSKI49ra zH^)IcG^?PPNdF(r9LCfAy@2SVFJ@*(ca@p{f^Ox$8d5_v!CbV?8l=bfWO>kJw`OT0?%c*bj9fe(mI&A5hHbu|bxF*RjW8fE- zV6XFJIT%G51sV8DvG;AkEKpBhosnzujr(zoT$9ZoWPn-{4?yhy55gF^Ca)HgoSgsA3f#GuoP7A9 z4XA?;Qq-U*Il1YPJ0sWRQ;#wkxh9)DE*3_#9N?Y%$-5p$Fmg@4XCygU=Lr|nBBRL< zHIyeuKT!m?fRq^*OkViJjgf2eLkmeB1}Iq4xYPoy%-|`=J0GnjxuG%)OB%OME_~|9 z$Tj($t>ol0Pq~;j*+P}FP1dvHVGrEqAD8M!9kf2Pdz(G@J9{#*szvQlQ$o?P%;9~2AT{!Gcq=U;F!UCx+%`vu6&j69W3SROtUYqRrQh2g;HYeO7?`+A*yI*oK@=vaRDKoh?n|Jd2muzZ?kOsHW7#SEO znHd=L;bt>1ENQ%$H(7DN=wyReN=V8PjR6J*T@>Zl3&6@93Z>w7uY0A;6d=3-D%T14WNHgE@n;Fav|{T@ViddaK1R diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e8be595e..6ec1567a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..79a61d42 100755 --- a/gradlew +++ b/gradlew @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac From 5b0fe2c006a2509aeb912d8c834db8aa6355c635 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 19:28:49 +0000 Subject: [PATCH 12/74] Bump ru.cian:huawei-publish-gradle-plugin from 1.3.5 to 1.4.0 (#2185) --- app/build.gradle | 8 +++++++- build.gradle | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ad83461a..f500e779 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -175,7 +175,13 @@ huaweiPublish { hmsRelease { credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json" buildFormat = "aab" - deployType = "draft" + deployType = "publish" + releaseNotes = [ + new ru.cian.huawei.publish.ReleaseNote( + "pl-PL", + "$projectDir/src/main/play/release-notes/pl-PL/default.txt" + ) + ] } } } diff --git a/build.gradle b/build.gradle index 88079e54..eac46b05 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { classpath 'com.huawei.agconnect:agcp:1.9.0.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' classpath "com.github.triplet.gradle:play-publisher:3.6.0" - classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" + classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" From ea312c3e12c9bbc3dc4a379d092678ea099dad89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 17:13:02 +0000 Subject: [PATCH 13/74] Bump androidx.core:core-ktx from 1.10.0 to 1.10.1 (#2208) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f500e779..aaa27b9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -203,7 +203,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" - implementation "androidx.core:core-ktx:1.10.0" + implementation "androidx.core:core-ktx:1.10.1" implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.7.1" implementation "androidx.appcompat:appcompat:1.6.1" From 8a7b7103eb375b22231a748a6360f85640033489 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 18:36:26 +0000 Subject: [PATCH 14/74] Bump coroutines from 1.7.0 to 1.7.1 (#2207) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index aaa27b9c..2cde7cda 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,7 +192,7 @@ ext { room = "2.5.1" chucker = "3.5.2" mockk = "1.13.5" - coroutines = "1.7.0" + coroutines = "1.7.1" } dependencies { From 48bcf581cfde80e282eb18866870c42be5e91c74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 18:36:50 +0000 Subject: [PATCH 15/74] Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2209) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2cde7cda..3b5fadb1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -200,7 +200,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.10.1" From 94664828938ee2b25cdd6a367323fe4c2923477a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 18 May 2023 16:14:14 +0200 Subject: [PATCH 16/74] Add foojay-resolver and update dependencies (#2212) --- app/build.gradle | 10 +++++----- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 ++++--- settings.gradle | 3 +++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3b5fadb1..83be53fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,11 +156,11 @@ android { kapt { correctErrorTypes true } + kotlin { - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } + jvmToolchain(11) } + play { defaultToAppBundles = false track = 'production' @@ -261,7 +261,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:22.0.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' + hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" @@ -275,7 +275,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.10.2' + testImplementation 'org.robolectric:robolectric:4.10.3' testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 8979 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 Date: Mon, 22 May 2023 17:09:57 +0200 Subject: [PATCH 17/74] Version 2.0.4 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 83be53fc..72948873 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 125 - versionName "2.0.3" + versionCode 126 + versionName "2.0.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -166,7 +166,7 @@ play { track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS userFraction = 0.50d - updatePriority = 3 + updatePriority = 1 enabled.set(false) } @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.3' + implementation 'io.github.wulkanowy:sdk:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 aee30290..858b44d4 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 2.0.3 +Wersja 2.0.4 — zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3 — dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym From aca88b57e0b005df6bf0f05efbe66dc066479451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 23 May 2023 02:16:42 +0200 Subject: [PATCH 18/74] Add r8 rules for HMS SDK (#2217) --- app/proguard-rules.pro | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index fd948261..ac7d1b02 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,6 @@ # General -dontobfuscate +-ignorewarnings #Config for wulkanowy @@ -24,3 +25,13 @@ #Config for Material Components -keep class com.google.android.material.tabs.** { *; } + +#Config for HMS SDK +-keepattributes *Annotation* +-keepattributes Exceptions +-keepattributes InnerClasses +-keepattributes Signature +-keep class com.huawei.agconnect.**{*;} +-keep class com.huawei.hianalytics.**{*;} +-keep class com.huawei.updatesdk.**{*;} +-keep class com.huawei.hms.**{*;} From 4c1fe233c7d4fa2ef4d4cd088fbfecc0189ae4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 23 May 2023 02:37:38 +0200 Subject: [PATCH 19/74] Version 2.0.5 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 72948873..b1116c58 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 126 - versionName "2.0.4" + versionCode 127 + versionName "2.0.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.4' + implementation 'io.github.wulkanowy:sdk:2.0.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 858b44d4..72095273 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 2.0.4 +Wersja 2.0.5 — zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3 — dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym From c1706144615b9e024f3e8d1a7efe47d60dd1aa35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 23 May 2023 14:09:48 +0200 Subject: [PATCH 20/74] Add R8 rule for Wulkanowy SDK (#2220) --- app/proguard-rules.pro | 5 +++++ gradle.properties | 29 +++++++---------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ac7d1b02..0fd49f6a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -26,6 +26,7 @@ #Config for Material Components -keep class com.google.android.material.tabs.** { *; } + #Config for HMS SDK -keepattributes *Annotation* -keepattributes Exceptions @@ -35,3 +36,7 @@ -keep class com.huawei.hianalytics.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;} + + +#Config for Wulkanowy SDK +-keep,allowobfuscation,allowshrinking class retrofit2.Response diff --git a/gradle.properties b/gradle.properties index 5a8099a1..4c54d414 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,28 +1,13 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # -android.enableJetifier=true -android.useAndroidX=true -android.defaults.buildfeatures.buildconfig=true -android.nonTransitiveRClass=false -android.nonFinalResIds=false -# -kotlin.code.style=official -kapt.use.worker.api=true kapt.include.compile.classpath=false +kotlin.code.style=official +# +android.useAndroidX=true +android.enableJetifier=true +android.nonTransitiveRClass=false +android.defaults.buildfeatures.buildconfig=true # # https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-common-faq-0000001063210244#section17273113244910 apmsInstrumentationEnabled=false -# -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true + From 092e86b621459ec4cb0229a6aa65f43e623ea85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 23 May 2023 16:26:31 +0200 Subject: [PATCH 21/74] Version 2.0.6 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b1116c58..5cebcb31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 127 - versionName "2.0.5" + versionCode 128 + versionName "2.0.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -165,8 +165,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS - userFraction = 0.50d - updatePriority = 1 + userFraction = 0.25d + updatePriority = 4 enabled.set(false) } @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.5' + implementation 'io.github.wulkanowy:sdk:2.0.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 72095273..df69a331 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 2.0.5 +Wersja 2.0.6 — zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3 — dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym From e7733bfa2a72362e8736dc102383cce9d241cf6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 21:31:38 +0000 Subject: [PATCH 22/74] Bump com.google.android.gms:play-services-ads from 22.0.0 to 22.1.0 (#2216) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5cebcb31..40ea252c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -259,7 +259,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-config-ktx' playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core-ktx:1.8.1' - playImplementation 'com.google.android.gms:play-services-ads:22.0.0' + playImplementation 'com.google.android.gms:play-services-ads:22.1.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' From 19ed12146686d95d15101c58429403f8245425ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 21:32:28 +0000 Subject: [PATCH 23/74] Bump io.coil-kt:coil from 2.3.0 to 2.4.0 (#2215) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 40ea252c..3b7ee510 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,7 +246,7 @@ dependencies { implementation "at.favre.lib:slf4j-timber:1.0.1" implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" - implementation "io.coil-kt:coil:2.3.0" + implementation "io.coil-kt:coil:2.4.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.9.1' From 3096fa15384ce6d8083d1aad5143be1ee1fed1ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 21:50:17 +0000 Subject: [PATCH 24/74] Bump about_libraries from 10.6.3 to 10.7.0 (#2214) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index eac46b05..1a70f82a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.8.21' - about_libraries = '10.6.3' + about_libraries = '10.7.0' hilt_version = "2.46.1" } repositories { From 70333737cf218734850ea5d4df16b439f75e789b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:16:42 +0000 Subject: [PATCH 25/74] Bump androidx.activity:activity-ktx from 1.7.1 to 1.7.2 (#2226) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3b7ee510..e72beed2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -205,7 +205,7 @@ dependencies { implementation "androidx.core:core-ktx:1.10.1" implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.7.1" + implementation "androidx.activity:activity-ktx:1.7.2" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.7" implementation "androidx.annotation:annotation:1.6.0" From 48e4a9fec58cc6beb07f7e086fe6fd964f318f66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:17:48 +0000 Subject: [PATCH 26/74] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2223) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1a70f82a..b7ac37ed 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.1.0.3113" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From db4e4d8cef31f399bfc5a4d48c0a1a98d1d53ffa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:18:07 +0000 Subject: [PATCH 27/74] Bump com.huawei.hms:hianalytics from 6.10.0.300 to 6.10.0.301 (#2224) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e72beed2..251aa4b6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -261,7 +261,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:22.1.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 06fd7b0c368959d101a3baf0df021becfa73222a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:18:30 +0000 Subject: [PATCH 28/74] Bump com.google.firebase:firebase-bom from 32.0.0 to 32.1.0 (#2225) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 251aa4b6..05070d10 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -252,7 +252,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:32.0.0') + playImplementation platform('com.google.firebase:firebase-bom:32.1.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 556f42195b1bc5d7c5ac9ba26ee4cf7c75f3e931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:18:55 +0000 Subject: [PATCH 29/74] Bump com.android.tools.build:gradle from 8.0.1 to 8.0.2 (#2228) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b7ac37ed..69c0905a 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.android.tools.build:gradle:8.0.1' + classpath 'com.android.tools.build:gradle:8.0.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.9.0.300' From 41bde45731dce8c4cf39200ff98e7ca47c6c7bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 15:19:25 +0000 Subject: [PATCH 30/74] Bump androidx.viewpager2:viewpager2 from 1.1.0-beta01 to 1.1.0-beta02 (#2227) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 05070d10..929a9db9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,7 +212,7 @@ dependencies { implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.recyclerview:recyclerview:1.3.0" - implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" + implementation "androidx.viewpager2:viewpager2:1.1.0-beta02" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" From 1bc0f2d21457281d1c528b7c6cb9697b3211f9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 1 Jun 2023 10:30:50 +0200 Subject: [PATCH 31/74] Add character limit to attendance excuse content (#2222) --- app/src/main/res/layout/dialog_excuse.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_excuse.xml b/app/src/main/res/layout/dialog_excuse.xml index 401aef31..8297bc14 100644 --- a/app/src/main/res/layout/dialog_excuse.xml +++ b/app/src/main/res/layout/dialog_excuse.xml @@ -1,5 +1,6 @@ + android:layout_height="wrap_content" + app:counterEnabled="true" + app:counterMaxLength="256"> + android:layout_weight="1" + android:hint="@string/attendance_excuse_dialog_reason" + android:maxLength="256" /> From 63487249b8ce8995e6b433ee31701ac9015d405b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 1 Jun 2023 10:31:42 +0200 Subject: [PATCH 32/74] New Crowdin updates (#2211) --- app/src/main/res/values-ru/strings.xml | 18 +++++++++--------- app/src/main/res/values-uk/strings.xml | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 726e9546..9029f4b9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -682,7 +682,7 @@ Отменить Нет уроков - Synchronized on %1$s at %2$s + Синхронизировано %1$s в %2$s Выбрать тему Светлая Тёмная @@ -778,7 +778,7 @@ Значения плюса и минуса, расчёт средней оценки Расширенные Версия приложения, разработчики, соц. сети - Displaying advertisements, project support + Посмотреть рекламу, чтобы поддержать проект Новые оценки Новое домашнее задание @@ -810,14 +810,14 @@ Для сохранения изменений необходимо перезапустить приложение Перезапустить - Authorization has been rejected. The data provided does not match the records in the secretary\'s office. - Invalid PESEL - PESEL + Авторизация отклонена. Предоставленные данные не соответствуют записям в кабинете секретаря. + Неправильный номер PESEL + Номер PESEL Authorize - Authorization completed successfully - Authorization - To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below - Skip for now + Авторизация прошла успешно + Авторизация + Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже + Пропустить сейчас Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 66cd2387..be136ea2 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -682,7 +682,7 @@ Скасувати Немаэ уроків - Synchronized on %1$s at %2$s + Синхронізовано %1$s в %2$s Увібрати тему Яскрава Темна @@ -810,14 +810,14 @@ Додаток потрібно перезавантажити для збереження змін Перезавантажити - Authorization has been rejected. The data provided does not match the records in the secretary\'s office. - Invalid PESEL - PESEL + Авторизацію відхилено. Надані дані не збігаються із записами в кабінеті секретаря. + Неправильний PESEL + Число PESEL Authorize Authorization completed successfully - Authorization - To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below - Skip for now + Авторизувати + Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче + Поки що пропустити Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою From d4ae0d56d62215ea26ad829579ac909d9e313575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 1 Jun 2023 10:59:50 +0200 Subject: [PATCH 33/74] Version 2.0.7 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 929a9db9..ec62e19f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 128 - versionName "2.0.6" + versionCode 129 + versionName "2.0.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -166,7 +166,7 @@ play { track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS userFraction = 0.25d - updatePriority = 4 + updatePriority = 1 enabled.set(false) } @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.6' + implementation 'io.github.wulkanowy:sdk:2.0.7' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 df69a331..066485d3 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,7 @@ -Wersja 2.0.6 +Wersja 2.0.7 -— zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3 -— dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym -— poprawiliśmy sposób wyświetlania błędu o nieprawidłowym haśle na ekranie logowania -— od teraz zmiana ustawień liczenia średniej automatycznie odświeży listę ocen -— dodaliśmy okienko na wpisanie numeru PESEL, gdy dziennik wymaga dodatkowej autoryzacji dostępu +— poprawiliśmy wyświetlanie kilku rodzajów zmian w planie lekcji +— dodaliśmy limit znaków w okienku usprawiedliwiania +— naprawiliśmy wyświetlanie frekwencji w szkołach, gdzie działa już system eduOne (ciągle jednak brak opcji usprawiedliwiania) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 5306044173d5a67b4d1d09f037148f08b4ea8574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 1 Jun 2023 23:22:10 +0200 Subject: [PATCH 34/74] Add custom register host field on login screen (#2221) --- app/build.gradle | 2 +- .../56.json | 2442 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../wulkanowy/data/db/entities/Student.kt | 3 + .../data/mappers/RegisterUserMapper.kt | 2 + .../data/repositories/StudentRepository.kt | 12 +- .../wulkanowy/ui/modules/login/LoginData.kt | 1 + .../login/advanced/LoginAdvancedFragment.kt | 7 + .../login/advanced/LoginAdvancedPresenter.kt | 6 +- .../login/advanced/LoginAdvancedView.kt | 2 + .../modules/login/form/LoginFormFragment.kt | 8 + .../modules/login/form/LoginFormPresenter.kt | 18 +- .../ui/modules/login/form/LoginFormView.kt | 4 + .../LoginStudentSelectPresenter.kt | 1 + .../login/symbol/LoginSymbolPresenter.kt | 1 + .../io/github/wulkanowy/utils/SdkExtension.kt | 1 + .../res/layout/fragment_login_advanced.xml | 29 +- .../main/res/layout/fragment_login_form.xml | 28 +- app/src/main/res/values/api_hosts.xml | 3 + app/src/main/res/values/strings.xml | 1 + .../io/github/wulkanowy/TestEnityCreator.kt | 1 + .../data/repositories/GradeRepositoryTest.kt | 1 + .../domain/GetMailboxByStudentUseCaseTest.kt | 1 + .../modules/grade/GradeAverageProviderTest.kt | 1 + .../login/form/LoginFormPresenterTest.kt | 13 +- .../LoginStudentSelectPresenterTest.kt | 1 + 26 files changed, 2564 insertions(+), 28 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json diff --git a/app/build.gradle b/app/build.gradle index ec62e19f..19fbf6de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.7' + implementation 'io.github.wulkanowy:sdk:2.0.8-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json new file mode 100644 index 00000000..1a26e717 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json @@ -0,0 +1,2442 @@ +{ + "formatVersion": 1, + "database": { + "version": 56, + "identityHash": "48f0538bd21601eb5322a7d850e04134", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `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": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "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, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `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": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "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": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "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": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_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": "user_login_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "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 + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_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": "userLoginId", + "columnName": "user_login_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `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": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `is_dismissible` INTEGER 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 + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "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, '48f0538bd21601eb5322a7d850e04134')" + ] + } +} \ 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 0aea86da..882a7016 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 @@ -49,6 +49,7 @@ import javax.inject.Singleton AutoMigration(from = 47, to = 48), AutoMigration(from = 51, to = 52), AutoMigration(from = 54, to = 55, spec = Migration55::class), + AutoMigration(from = 55, to = 56), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -57,7 +58,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 55 + const val VERSION_SCHEMA = 56 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt index 76da9643..e1116733 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt @@ -19,6 +19,9 @@ data class Student( @ColumnInfo(name = "scrapper_base_url") val scrapperBaseUrl: String, + @ColumnInfo(name = "scrapper_domain_suffix", defaultValue = "") + val scrapperDomainSuffix: String, + @ColumnInfo(name = "mobile_base_url") val mobileBaseUrl: String, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt index bcf26a5e..72c4861c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt @@ -55,6 +55,7 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser( fun RegisterStudent.mapToStudentWithSemesters( user: RegisterUser, + scrapperDomainSuffix: String, symbol: RegisterSymbol, unit: RegisterUnit, colors: List, @@ -76,6 +77,7 @@ fun RegisterStudent.mapToStudentWithSemesters( studentName = "$studentName $studentSurname", loginMode = user.loginMode.name, scrapperBaseUrl = user.scrapperBaseUrl.orEmpty(), + scrapperDomainSuffix = scrapperDomainSuffix, mobileBaseUrl = symbol.hebeBaseUrl.orEmpty(), certificateKey = symbol.keyId.orEmpty(), privateKey = symbol.privatePem.orEmpty(), 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 a6bb7243..42d1eb84 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 @@ -43,22 +43,14 @@ class StudentRepository @Inject constructor( .getStudentsFromHebe(token, pin, symbol, "") .mapToPojo(null) - suspend fun getStudentsScrapper( - email: String, - password: String, - scrapperBaseUrl: String, - symbol: String - ): RegisterUser = sdk - .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) - .mapToPojo(password) - suspend fun getUserSubjectsFromScrapper( email: String, password: String, scrapperBaseUrl: String, + domainSuffix: String, symbol: String ): RegisterUser = sdk - .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) + .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol) .mapToPojo(password) suspend fun getStudentsHybrid( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt index ae6c2249..2c11bb6d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginData.kt @@ -6,5 +6,6 @@ data class LoginData( val login: String, val password: String, val baseUrl: String, + val domainSuffix: String, val symbol: String?, ) : Serializable 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 ead2d71a..13d2c14a 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 @@ -5,6 +5,7 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.widget.ArrayAdapter +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -55,6 +56,9 @@ class LoginAdvancedFragment : get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) .orEmpty() + override val formDomainSuffix: String + get() = binding.loginFormDomainSuffix.text.toString().trim() + override val formHostSymbol: String get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) .orEmpty() @@ -279,6 +283,7 @@ class LoginAdvancedFragment : loginFormUsernameLayout.visibility = VISIBLE loginFormPassLayout.visibility = VISIBLE loginFormHostLayout.visibility = VISIBLE + loginFormDomainSuffixLayout.isVisible = true loginFormPinLayout.visibility = GONE loginFormSymbolLayout.visibility = VISIBLE loginFormTokenLayout.visibility = GONE @@ -290,6 +295,7 @@ class LoginAdvancedFragment : loginFormUsernameLayout.visibility = VISIBLE loginFormPassLayout.visibility = VISIBLE loginFormHostLayout.visibility = VISIBLE + loginFormDomainSuffixLayout.isVisible = true loginFormPinLayout.visibility = GONE loginFormSymbolLayout.visibility = VISIBLE loginFormTokenLayout.visibility = GONE @@ -301,6 +307,7 @@ class LoginAdvancedFragment : loginFormUsernameLayout.visibility = GONE loginFormPassLayout.visibility = GONE loginFormHostLayout.visibility = GONE + loginFormDomainSuffixLayout.isVisible = false loginFormPinLayout.visibility = VISIBLE loginFormSymbolLayout.visibility = VISIBLE loginFormTokenLayout.visibility = VISIBLE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index ab56bd78..a17ad003 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -154,6 +154,7 @@ class LoginAdvancedPresenter @Inject constructor( login = view?.formUsernameValue.orEmpty().trim(), password = view?.formPassValue.orEmpty().trim(), baseUrl = view?.formHostValue.orEmpty().trim(), + domainSuffix = view?.formDomainSuffix.orEmpty().trim(), symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), ) when (it.data.symbols.size) { @@ -186,6 +187,7 @@ class LoginAdvancedPresenter @Inject constructor( val email = view?.formUsernameValue.orEmpty() val password = view?.formPassValue.orEmpty() val endpoint = view?.formHostValue.orEmpty() + val domainSuffix = view?.formDomainSuffix.orEmpty() val pin = view?.formPinValue.orEmpty() val symbol = view?.formSymbolValue.orEmpty() @@ -193,8 +195,8 @@ class LoginAdvancedPresenter @Inject constructor( return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { Sdk.Mode.HEBE -> studentRepository.getStudentsApi(pin, symbol, token) - Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper( - email, password, endpoint, symbol + Sdk.Mode.SCRAPPER -> studentRepository.getUserSubjectsFromScrapper( + email, password, endpoint, domainSuffix, symbol ) Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt index 34062d93..afd33e3b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedView.kt @@ -12,6 +12,8 @@ interface LoginAdvancedView : BaseView { val formHostValue: String + val formDomainSuffix: String + val formHostSymbol: String val formLoginType: String 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 43ba3fe1..1085ff50 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 @@ -45,6 +45,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) .orEmpty() + override val formDomainSuffix: String + get() = binding.loginFormDomainSuffix.text.toString() + override val formHostSymbol: String get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString())) .orEmpty() @@ -204,6 +207,10 @@ class LoginFormFragment : BaseFragment(R.layout.fragme binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } + override fun showDomainSuffixInput(show: Boolean) { + binding.loginFormDomainSuffixLayout.isVisible = show + } + override fun showOtherOptionsButton(show: Boolean) { binding.loginFormAdvancedButton.isVisible = show } @@ -256,6 +263,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme override fun onResume() { super.onResume() presenter.updateUsernameLabel() + presenter.updateCustomDomainSuffixVisibility() } override fun openEmail(lastError: String) { 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 ed70eb12..85f42841 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 @@ -1,8 +1,13 @@ package io.github.wulkanowy.ui.modules.login.form import androidx.core.net.toUri -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceLoading +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -56,10 +61,17 @@ class LoginFormPresenter @Inject constructor( } else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") { setCredentials("", "") } + updateCustomDomainSuffixVisibility() updateUsernameLabel() } } + fun updateCustomDomainSuffixVisibility() { + view?.run { + showDomainSuffixInput("customSuffix" in formHostValue) + } + } + fun updateUsernameLabel() { view?.run { setUsernameLabel(if ("email" !in formHostValue) nicknameLabel else emailLabel) @@ -91,6 +103,7 @@ class LoginFormPresenter @Inject constructor( val email = view?.formUsernameValue.orEmpty().trim() val password = view?.formPassValue.orEmpty().trim() val host = view?.formHostValue.orEmpty().trim() + val domainSuffix = view?.formDomainSuffix.orEmpty().trim() val symbol = view?.formHostSymbol.orEmpty().trim() if (!validateCredentials(email, password, host)) return @@ -100,6 +113,7 @@ class LoginFormPresenter @Inject constructor( email = email, password = password, scrapperBaseUrl = host, + domainSuffix = domainSuffix, symbol = symbol ) } @@ -112,7 +126,7 @@ class LoginFormPresenter @Inject constructor( } } .onResourceSuccess { - val loginData = LoginData(email, password, host, symbol) + val loginData = LoginData(email, password, host, domainSuffix, symbol) when (it.symbols.size) { 0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(loginData, it) 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 e5c680d6..5fb26062 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 @@ -14,6 +14,8 @@ interface LoginFormView : BaseView { val formHostValue: String + val formDomainSuffix: String + val formHostSymbol: String val nicknameLabel: String @@ -56,6 +58,8 @@ interface LoginFormView : BaseView { fun showContent(show: Boolean) + fun showDomainSuffixInput(show: Boolean) + fun showOtherOptionsButton(show: Boolean) fun showVersion() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 7e1fe3b2..70862e82 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -241,6 +241,7 @@ class LoginStudentSelectPresenter @Inject constructor( item.student.mapToStudentWithSemesters( user = registerUser, symbol = item.symbol, + scrapperDomainSuffix = loginData.domainSuffix, unit = item.unit, colors = appInfo.defaultColorsForAvatar, ) 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 03ea95fa..91fe1ac3 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 @@ -62,6 +62,7 @@ class LoginSymbolPresenter @Inject constructor( email = loginData.login, password = loginData.password, scrapperBaseUrl = loginData.baseUrl, + domainSuffix = loginData.domainSuffix, symbol = loginData.symbol.orEmpty(), ) }.onEach { user -> diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt index 481cad11..889d64ea 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -16,6 +16,7 @@ fun Sdk.init(student: Student): Sdk { mobileBaseUrl = student.mobileBaseUrl } else { scrapperBaseUrl = student.scrapperBaseUrl + domainSuffix = student.scrapperDomainSuffix loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) } diff --git a/app/src/main/res/layout/fragment_login_advanced.xml b/app/src/main/res/layout/fragment_login_advanced.xml index 43016db4..37551dec 100644 --- a/app/src/main/res/layout/fragment_login_advanced.xml +++ b/app/src/main/res/layout/fragment_login_advanced.xml @@ -172,7 +172,7 @@ android:layout_marginBottom="16dp" android:hint="@string/login_host_hint" android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@+id/loginFormTokenLayout" + app:layout_constraintBottom_toTopOf="@+id/loginFormDomainSuffixLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/loginFormPassLayout"> @@ -185,6 +185,31 @@ tools:ignore="Deprecated,LabelFor" /> + + + + + + app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout"> + + + + + + app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout" /> Gmina Ulan-Majorat - Platforma oświatowa Gmina Ozorków - Platforma edukacyjna Gmina Łopiennik Górny - Platforma oświatowa + Custom domain suffix Fakelog @@ -42,6 +43,7 @@ https://vulcan.net.pl/?login https://vulcan.net.pl/?login https://vulcan.net.pl/?login + https://vulcan.net.pl/?email&customSuffix https://fakelog.cf/?email @@ -64,6 +66,7 @@ gminaulanmajorat gminaozorkow gminalopiennikgorny + Default powiatwulkanowy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1eff9569..98c316cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,6 +42,7 @@ Login, PESEL or e-mail Password UONET+ register variant + Custom domain suffix Mobile API Scraper Hybrid diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index c8d95829..eac1389f 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -50,6 +50,7 @@ fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalD fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.HEBE) = Student( scrapperBaseUrl = "http://fakelog.cf", + scrapperDomainSuffix = "", email = "jan@fakelog.cf", certificateKey = "", classId = 0, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt index 1d6dfaff..5a1877cc 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt @@ -296,6 +296,7 @@ class GradeRepositoryTest { private fun createGrades(grades: List): Grades = Grades( details = grades, summary = listOf(), + descriptive = emptyList(), isAverage = false, isPoints = false, isForAdults = false, diff --git a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt index f58a5381..34a8fe99 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -201,6 +201,7 @@ class GetMailboxByStudentUseCaseTest { schoolShortName: String = "test", ) = Student( scrapperBaseUrl = "http://fakelog.cf", + scrapperDomainSuffix = "", email = "jan@fakelog.cf", certificateKey = "", classId = 0, diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index b94002c0..31ea3322 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -47,6 +47,7 @@ class GradeAverageProviderTest { private val student = Student( scrapperBaseUrl = "", + scrapperDomainSuffix = "", mobileBaseUrl = "", loginType = "", loginMode = "SCRAPPER", diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt index eb1f5300..a6440f72 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt @@ -130,7 +130,7 @@ class LoginFormPresenterTest { @Test fun loginTest() { coEvery { - repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + repository.getUserSubjectsFromScrapper(any(), any(), any(), any(), any()) } returns registerUser every { loginFormView.formUsernameValue } returns "@" @@ -149,7 +149,7 @@ class LoginFormPresenterTest { @Test fun loginEmptyTest() { coEvery { - repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + repository.getUserSubjectsFromScrapper(any(), any(), any(), any(), any()) } returns registerUser every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" @@ -167,7 +167,7 @@ class LoginFormPresenterTest { @Test fun loginEmptyTwiceTest() { coEvery { - repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + repository.getUserSubjectsFromScrapper(any(), any(), any(), any(), any()) } returns registerUser every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" @@ -187,12 +187,7 @@ class LoginFormPresenterTest { fun loginErrorTest() { val testException = IOException("test") coEvery { - repository.getUserSubjectsFromScrapper( - any(), - any(), - any(), - any() - ) + repository.getUserSubjectsFromScrapper(any(), any(), any(), any(), any()) } throws testException every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index da292c51..06aabec7 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -55,6 +55,7 @@ class LoginStudentSelectPresenterTest { password = "", baseUrl = "", symbol = null, + domainSuffix = "", ) private val subject = RegisterStudent( From fa44295d59ffa0b480f43b46b3139f10df88ebc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 1 Jun 2023 23:37:01 +0200 Subject: [PATCH 35/74] New Crowdin updates (#2231) --- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-da-rDK/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-es-rES/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + 8 files changed, 8 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index f8c19dff..964329da 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -37,6 +37,7 @@ Přihlášení, číslo PESEL nebo e-mail Heslo Variace deníku UONET+ + Custom domain suffix Mobile API Scraper Hybridní diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index b9de57f7..5c7d02a0 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -37,6 +37,7 @@ Login, PESEL or e-mail Password UONET+ register variant + Custom domain suffix Mobile API Scraper Hybrid diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 500553e2..96423e35 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -37,6 +37,7 @@ Anmeldung, PESEL oder e-mail Passwort UONET+ Registervariante + Custom domain suffix Mobile API Scraper Hybride diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index b9de57f7..5c7d02a0 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -37,6 +37,7 @@ Login, PESEL or e-mail Password UONET+ register variant + Custom domain suffix Mobile API Scraper Hybrid diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 207b12e9..2c707797 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -37,6 +37,7 @@ Login, PESEL lub adres e-mail Hasło Odmiana dziennika UONET+ + Niestandardowy sufiks domeny Mobilne API Scraper Hybrydowe diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9029f4b9..eb8be002 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -37,6 +37,7 @@ Логин, PESEL или электронная почта Пароль Тип дневника UONET+ + Custom domain suffix Мобильный API Scraper Hybrid diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 950eb01e..bcbd832a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -37,6 +37,7 @@ Prihlásenie, číslo PESEL alebo e-mail Heslo Variácia denníka UONET+ + Custom domain suffix Mobile API Scraper Hybridné diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index be136ea2..30a587cc 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -37,6 +37,7 @@ Логін, PESEL або e-mail Пароль Тип щоденника UONET+ + Custom domain suffix Мobile API Scraper Hybrid From fecd5c707d6005e37117d231d612ee59a92126c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 1 Jun 2023 23:43:01 +0200 Subject: [PATCH 36/74] Version 2.0.8 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- app/src/main/res/values/api_hosts.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 19fbf6de..000cc09c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 129 - versionName "2.0.7" + versionCode 130 + versionName "2.0.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -196,7 +196,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.8-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.0.8' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 066485d3..e881cfda 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 2.0.7 +Wersja 2.0.8 — poprawiliśmy wyświetlanie kilku rodzajów zmian w planie lekcji — dodaliśmy limit znaków w okienku usprawiedliwiania diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 93209367..522b6e11 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -20,7 +20,7 @@ Gmina Ulan-Majorat - Platforma oświatowa Gmina Ozorków - Platforma edukacyjna Gmina Łopiennik Górny - Platforma oświatowa - Custom domain suffix + @string/login_domain_suffix_hint Fakelog From 2f749a690b4293a802c6fc00063c41a42c5008c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 23:18:23 +0000 Subject: [PATCH 37/74] Bump androidx.fragment:fragment-ktx from 1.5.7 to 1.6.0 (#2239) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 000cc09c..2231da52 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -207,7 +207,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.7.2" implementation "androidx.appcompat:appcompat:1.6.1" - implementation "androidx.fragment:fragment-ktx:1.5.7" + implementation "androidx.fragment:fragment-ktx:1.6.0" implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" From f20ffe44d5b16c9954d9738b8f44214968f27853 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 23:24:27 +0000 Subject: [PATCH 38/74] Bump kotlin_version from 1.8.21 to 1.8.22 (#2238) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 69c0905a..c5a48ec9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.8.21' + kotlin_version = '1.8.22' about_libraries = '10.7.0' hilt_version = "2.46.1" } From 8913b22a200685915c4da82a10802ab5ad0a7990 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jun 2023 23:25:46 +0000 Subject: [PATCH 39/74] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2237) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c5a48ec9..757910b1 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.1.0.3113" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.0.3129" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From df8849639b3baa8826eafc3a2c1c7a8eccdc510d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:56:32 +0000 Subject: [PATCH 40/74] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2242) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 757910b1..150bd4cf 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.0.3129" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.1.3168" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From 03cd3aeab78cf3c3991cb28099cca6d67d5d2f1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:53:21 +0000 Subject: [PATCH 41/74] Bump com.google.firebase:firebase-bom from 32.1.0 to 32.2.0 (#2259) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2231da52..46f709c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -252,7 +252,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:32.1.0') + playImplementation platform('com.google.firebase:firebase-bom:32.2.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 6e7c12a118775f9590a57e99ac6a4064116505d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:53:38 +0000 Subject: [PATCH 42/74] Bump com.google.firebase:firebase-crashlytics-gradle from 2.9.5 to 2.9.7 (#2258) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 150bd4cf..62f6389f 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.9.0.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.7' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.1.3168" From bb79b33b6d40d0d208b7fe546c32a04a09d02446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:53:58 +0000 Subject: [PATCH 43/74] Bump com.google.android.gms:play-services-ads from 22.1.0 to 22.2.0 (#2256) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 46f709c8..4589331f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -259,7 +259,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-config-ktx' playImplementation 'com.google.android.play:core:1.10.3' playImplementation 'com.google.android.play:core-ktx:1.8.1' - playImplementation 'com.google.android.gms:play-services-ads:22.1.0' + playImplementation 'com.google.android.gms:play-services-ads:22.2.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' From dbe608f2dd6aa9ab616c40a5d0b377a88d77fe37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:54:26 +0000 Subject: [PATCH 44/74] Bump about_libraries from 10.7.0 to 10.8.0 (#2249) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 62f6389f..d12932db 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.8.22' - about_libraries = '10.7.0' + about_libraries = '10.8.0' hilt_version = "2.46.1" } repositories { From 29a36aaf6ed30318e2a8feb4666a0739daed822d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:11:57 +0000 Subject: [PATCH 45/74] Bump com.huawei.hms:hianalytics from 6.10.0.301 to 6.10.0.302 (#2253) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4589331f..54cc97ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -261,7 +261,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:22.2.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.301' + hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.302' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 8564e12b015b151f3ead4b13f7ae4fb5d4c8358d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:15:06 +0000 Subject: [PATCH 46/74] Bump com.huawei.agconnect:agcp from 1.9.0.300 to 1.9.1.300 (#2254) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d12932db..75191aad 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.0.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' - classpath 'com.huawei.agconnect:agcp:1.9.0.300' + classpath 'com.huawei.agconnect:agcp:1.9.1.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.7' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" From d0819928f3b61e4d020d2dd9b78d10fd9b32d4d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:15:21 +0000 Subject: [PATCH 47/74] Bump com.huawei.agconnect:agconnect-crash from 1.9.0.300 to 1.9.1.300 (#2252) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 54cc97ff..fdb8d20f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -262,7 +262,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:22.2.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.302' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 88ea753fc69bba48fb367a9535e28c05e14c6b6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:15:34 +0000 Subject: [PATCH 48/74] Bump coroutines from 1.7.1 to 1.7.2 (#2251) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index fdb8d20f..9e074371 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,7 +192,7 @@ ext { room = "2.5.1" chucker = "3.5.2" mockk = "1.13.5" - coroutines = "1.7.1" + coroutines = "1.7.2" } dependencies { From 86c7de6595fc6f71dbe616bffaa5474d1bdb215e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:15:48 +0000 Subject: [PATCH 49/74] Bump room from 2.5.1 to 2.5.2 (#2250) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 9e074371..f3ea03c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,7 +189,7 @@ huaweiPublish { ext { work_manager = "2.8.1" android_hilt = "1.0.0" - room = "2.5.1" + room = "2.5.2" chucker = "3.5.2" mockk = "1.13.5" coroutines = "1.7.2" From 05741761a2117667d848d171b43096287d1aa811 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:08:56 +0000 Subject: [PATCH 50/74] Bump kotlin_version from 1.8.22 to 1.9.0 (#2255) --- app/build.gradle | 15 +++++---------- .../wulkanowy/data/db/dao/AdminMessageDao.kt | 2 +- build.gradle | 3 ++- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f3ea03c1..596393f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlinx-serialization' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' @@ -33,14 +34,6 @@ android { firebase_enabled: project.hasProperty("enableFirebase"), admob_project_id: "" ] - javaCompileOptions { - annotationProcessorOptions { - arguments += [ - "room.schemaLocation": "$projectDir/schemas".toString(), - "room.incremental" : "true" - ] - } - } buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null" @@ -156,7 +149,9 @@ android { kapt { correctErrorTypes true } - +ksp { + arg("room.schemaLocation", "$projectDir/schemas".toString()) +} kotlin { jvmToolchain(11) } @@ -228,7 +223,7 @@ dependencies { implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" - kapt "androidx.room:room-compiler:$room" + ksp "androidx.room:room-compiler:$room" implementation "com.google.dagger:hilt-android:$hilt_version" kapt "com.google.dagger:hilt-android-compiler:$hilt_version" 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 index 87f4812d..2b4cb597 100644 --- 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 @@ -22,4 +22,4 @@ abstract class AdminMessageDao : BaseDao { deleteAll(oldMessages) insertAll(newMessages) } -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index 75191aad..6a378d48 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.8.22' + kotlin_version = '1.9.0' about_libraries = '10.8.0' hilt_version = "2.46.1" } @@ -13,6 +13,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.11" classpath 'com.android.tools.build:gradle:8.0.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' From ef72218906b6a87d7a909fb3a561d99307276c17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:30:37 +0000 Subject: [PATCH 51/74] Bump org.gradle.toolchains.foojay-resolver-convention (#2264) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index a69aaa95..af9bb737 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0' } include ':app' From c0161f38c61dc93a338b5907b374a48280e65031 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:33:17 +0000 Subject: [PATCH 52/74] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2262) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6a378d48..8bdcdedf 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.7' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.1.3168" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.3.0.3225" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From e79c5d4d2bc9b7cb9359377fb7709bdc2633f8c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:36:06 +0000 Subject: [PATCH 53/74] Bump hilt_version from 2.46.1 to 2.47 (#2261) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8bdcdedf..dba02152 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.0' about_libraries = '10.8.0' - hilt_version = "2.46.1" + hilt_version = "2.47" } repositories { mavenCentral() From 5b2e2ffb34820e475a4553eb1795b070a4f0a789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 25 Jul 2023 11:37:43 +0200 Subject: [PATCH 54/74] Remove tests deprecations (#2260) --- app/build.gradle | 2 +- .../io/github/wulkanowy/MainCoroutineRule.kt | 12 +++---- .../db/migrations/AbstractMigrationTest.kt | 3 +- .../data/db/migrations/Migration12Test.kt | 18 +++++----- .../data/db/migrations/Migration13Test.kt | 34 +++++++++++-------- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 596393f5..f8603cc8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlinx-serialization' apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.gms.google-services' @@ -11,6 +10,7 @@ apply plugin: 'com.github.triplet.play' apply plugin: 'ru.cian.huawei-publish' apply plugin: 'com.mikepenz.aboutlibraries.plugin' apply plugin: 'com.huawei.agconnect' +apply plugin: 'kotlin-kapt' apply from: 'jacoco.gradle' apply from: 'sonarqube.gradle' apply from: 'hooks.gradle' diff --git a/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt b/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt index 10724868..543c9540 100644 --- a/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt +++ b/app/src/test/java/io/github/wulkanowy/MainCoroutineRule.kt @@ -2,7 +2,8 @@ package io.github.wulkanowy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.rules.TestWatcher @@ -10,17 +11,14 @@ import org.junit.runner.Description @OptIn(ExperimentalCoroutinesApi::class) class MainCoroutineRule( - private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() + private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher() ) : TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) + override fun starting(description: Description) { Dispatchers.setMain(testDispatcher) } - override fun finished(description: Description?) { - super.finished(description) + override fun finished(description: Description) { Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() } } 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 18249ba8..18ff9339 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 @@ -22,7 +22,8 @@ abstract class AbstractMigrationTest { @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), - AppDatabase::class.java.canonicalName, + AppDatabase::class.java, + listOf(Migration55()), FrameworkSQLiteOpenHelperFactory() ) diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration12Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration12Test.kt index f614c8ca..54c73e20 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration12Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration12Test.kt @@ -22,12 +22,12 @@ class Migration12Test : AbstractMigrationTest() { fun twoNotRelatedStudents() { helper.createDatabase(dbName, 11).apply { // user 1 - createStudent(this, 1, true) + createStudent(this, 1) createSemester(this, 1, false, 5, 1) createSemester(this, 1, true, 5, 2) // user 2 - createStudent(this, 2, true) + createStudent(this, 2) createSemester(this, 2, false, 6, 1) createSemester(this, 2, true, 6, 2) close() @@ -56,9 +56,9 @@ class Migration12Test : AbstractMigrationTest() { fun removeStudentsWithoutClassId() { helper.createDatabase(dbName, 11).apply { // user 1 - createStudent(this, 1, true) + createStudent(this, 1) createSemester(this, 1, false, 0, 2) - createStudent(this, 2, true) + createStudent(this, 2) createSemester(this, 2, true, 1, 2) close() } @@ -81,11 +81,11 @@ class Migration12Test : AbstractMigrationTest() { fun ensureThereIsOnlyOneCurrentStudent() { helper.createDatabase(dbName, 11).apply { // user 1 - createStudent(this, 1, true) + createStudent(this, 1) createSemester(this, 1, true, 5, 2) - createStudent(this, 2, true) + createStudent(this, 2) createSemester(this, 2, true, 6, 2) - createStudent(this, 3, true) + createStudent(this, 3) createSemester(this, 3, false, 7, 2) close() } @@ -112,7 +112,7 @@ class Migration12Test : AbstractMigrationTest() { db.close() } - private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean) { + private fun createStudent(db: SupportSQLiteDatabase, studentId: Int) { db.insert("Students", CONFLICT_FAIL, ContentValues().apply { put("endpoint", "https://fakelog.cf") put("loginType", "STANDARD") @@ -123,7 +123,7 @@ class Migration12Test : AbstractMigrationTest() { put("student_name", "Jan Kowalski") put("school_id", "000123") put("school_name", "") - put("is_current", isCurrent) + put("is_current", true) put("registration_date", "0") }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt index b0c03fb1..9ba36876 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration13Test.kt @@ -95,22 +95,22 @@ class Migration13Test : AbstractMigrationTest() { fun markAtLeastAndOnlyOneSemesterAtCurrent() { helper.createDatabase(dbName, 12).apply { createStudent(this, 1, "", 5) - createSemester(this, 1, 5, 1, 1, false) - createSemester(this, 1, 5, 2, 1, false) - createSemester(this, 1, 5, 3, 2, false) - createSemester(this, 1, 5, 4, 2, false) + createSemester(this, 1, 1, 1, false) + createSemester(this, 1, 2, 1, false) + createSemester(this, 1, 3, 2, false) + createSemester(this, 1, 4, 2, false) createStudent(this, 2, "", 5) - createSemester(this, 2, 5, 5, 5, true) - createSemester(this, 2, 5, 6, 5, true) - createSemester(this, 2, 5, 7, 55, true) - createSemester(this, 2, 5, 8, 55, true) + createSemester(this, 2, 5, 5, true) + createSemester(this, 2, 6, 5, true) + createSemester(this, 2, 7, 55, true) + createSemester(this, 2, 8, 55, true) createStudent(this, 3, "", 5) - createSemester(this, 3, 5, 11, 99, false) - createSemester(this, 3, 5, 12, 99, false) - createSemester(this, 3, 5, 13, 100, false) - createSemester(this, 3, 5, 14, 100, true) + createSemester(this, 3, 11, 99, false) + createSemester(this, 3, 12, 99, false) + createSemester(this, 3, 13, 100, false) + createSemester(this, 3, 14, 100, true) close() } @@ -198,7 +198,13 @@ class Migration13Test : AbstractMigrationTest() { }) } - private fun createSemester(db: SupportSQLiteDatabase, studentId: Int, classId: Int, semesterId: Int, diaryId: Int, isCurrent: Boolean = false) { + private fun createSemester( + db: SupportSQLiteDatabase, + studentId: Int, + semesterId: Int, + diaryId: Int, + isCurrent: Boolean = false + ) { db.insert("Semesters", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("student_id", studentId) put("diary_id", diaryId) @@ -206,7 +212,7 @@ class Migration13Test : AbstractMigrationTest() { put("semester_id", semesterId) put("semester_name", "1") put("is_current", isCurrent) - put("class_id", classId) + put("class_id", 5) put("unit_id", "99") }) } From 398bc513fb6e6e7284a1e1126f01af755ade050f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:00:31 +0000 Subject: [PATCH 55/74] Bump about_libraries from 10.8.0 to 10.8.3 (#2263) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dba02152..9584caac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.0' - about_libraries = '10.8.0' + about_libraries = '10.8.3' hilt_version = "2.47" } repositories { From b2969264231a65109ed70b24c7636c0c3425802a Mon Sep 17 00:00:00 2001 From: Bartosz Bieniek Date: Tue, 25 Jul 2023 23:05:14 +0200 Subject: [PATCH 56/74] Timetable widget improvements (#2219) --- .../timetablewidget/TimetableWidgetFactory.kt | 8 +- .../TimetableWidgetProvider.kt | 270 +++++++++------ .../background_timetable_widget_avatar.xml | 2 +- .../background_widget_item_timetable.xml | 2 +- .../res/drawable/ic_timetable_widget_swap.xml | 4 +- .../main/res/drawable/ic_widget_chevron.xml | 4 +- .../drawable/img_timetable_widget_preview.png | Bin 21111 -> 76378 bytes .../layout-v31/widget_timetable_preview.xml | 321 ++++++++++++++++-- .../main/res/layout/item_widget_timetable.xml | 20 +- app/src/main/res/layout/widget_timetable.xml | 89 ++--- .../res/xml/provider_widget_timetable.xml | 10 +- 11 files changed, 524 insertions(+), 206 deletions(-) mode change 100755 => 100644 app/src/main/res/drawable/img_timetable_widget_preview.png diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 9c5abe1c..d545413d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -124,14 +124,12 @@ class TimetableWidgetFactory( val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE) val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE) - val roomText = "${context.getString(R.string.timetable_room)} ${lesson.room}" val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime) setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime) setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) - setTextViewText(R.id.timetableWidgetItemRoom, roomText) setTextViewText(R.id.timetableWidgetItemTeacher, lesson.teacher) setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent()) @@ -140,6 +138,12 @@ class TimetableWidgetFactory( updateTheme() clearLessonStyles(remoteViews) + if (lesson.room.isBlank()) { + remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + } else { + remoteViews.setTextViewText(R.id.timetableWidgetItemRoom, lesson.room) + } + when { lesson.canceled -> applyCancelledLessonStyles(remoteViews) lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles( 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 624ca30f..cc48539a 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 @@ -8,6 +8,7 @@ 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.graphics.Bitmap import android.widget.RemoteViews import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.drawable.DrawableCompat @@ -76,110 +77,151 @@ class TimetableWidgetProvider : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { GlobalScope.launch { when (intent.action) { - ACTION_APPWIDGET_UPDATE -> onUpdate(context, intent) - ACTION_APPWIDGET_DELETED -> onDelete(intent) + ACTION_APPWIDGET_UPDATE -> onWidgetUpdate(context, intent) + ACTION_APPWIDGET_DELETED -> onWidgetDeleted(intent) } } } - private suspend fun onUpdate(context: Context, intent: Intent) { - if (intent.getStringExtra(EXTRA_BUTTON_TYPE) == null) { - val isFromConfigure = intent.getBooleanExtra(EXTRA_FROM_CONFIGURE, false) - val appWidgetIds = intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS) ?: return + private suspend fun onWidgetUpdate(context: Context, intent: Intent) { + val pressedButton = intent.getPressedButton() - appWidgetIds.forEach { appWidgetId -> - val student = - getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - val savedDataEpochDay = sharedPref.getLong(getDateWidgetKey(appWidgetId), 0) - - val dateToLoad = if (isFromConfigure && savedDataEpochDay != 0L) { - LocalDate.ofEpochDay(savedDataEpochDay) - } else { - getWidgetDefaultDateToLoad(appWidgetId) - } - - updateWidget(context, appWidgetId, dateToLoad, student) - } + if (pressedButton == null) { + val updatedWidgetIds = intent.getWidgetIds() ?: return + updatedWidgetIds.forEach { updateWidgetLayout(context, it) } } else { - val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) - val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) - val student = getStudent( - sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId - ) - val savedDate = - LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) - val date = when (buttonType) { - BUTTON_RESET -> getWidgetDefaultDateToLoad(toggledWidgetId) - BUTTON_NEXT -> savedDate.nextSchoolDay - BUTTON_PREV -> savedDate.previousSchoolDay - else -> getWidgetDefaultDateToLoad(toggledWidgetId) - } - if (!buttonType.isNullOrBlank()) { - analytics.logEvent( - "changed_timetable_widget_day", "button" to buttonType - ) - } - updateWidget(context, toggledWidgetId, date, student) + val widgetId = intent.getToggledWidgetId() ?: return + reportChangedDay(pressedButton) + updateSavedWidgetDate(widgetId, pressedButton) + updateWidgetLayout(context, widgetId) } } - private fun onDelete(intent: Intent) { - val appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0) + private fun Intent.getPressedButton(): String? { + return getStringExtra(EXTRA_BUTTON_TYPE) + } - if (appWidgetId != 0) { - with(sharedPref) { - delete(getStudentWidgetKey(appWidgetId)) - delete(getDateWidgetKey(appWidgetId)) - } + private fun Intent.getWidgetIds(): IntArray? { + return getIntArrayExtra(EXTRA_APPWIDGET_IDS) + } + + private fun Intent.getToggledWidgetId(): Int? { + val toggledWidgetId = getIntExtra(EXTRA_TOGGLED_WIDGET_ID, INVALID_APPWIDGET_ID) + return toggledWidgetId.takeIf { it != INVALID_APPWIDGET_ID } + } + + private fun reportChangedDay(buttonType: String) { + if (buttonType.isNotBlank()) { + analytics.logEvent("changed_timetable_widget_day", "button" to buttonType) } } - private fun updateWidget( - context: Context, appWidgetId: Int, date: LocalDate, student: Student? + private fun updateSavedWidgetDate(widgetId: Int, buttonType: String) { + val savedDate = getSavedWidgetDate(widgetId) + val newDate = savedDate?.let { getNewDate(it, widgetId, buttonType) } + ?: getWidgetDefaultDateToLoad(widgetId) + setWidgetDate(widgetId, newDate) + } + + private fun getSavedWidgetDate(widgetId: Int): LocalDate? { + val epochDay = sharedPref.getLong(getDateWidgetKey(widgetId), 0) + return if (epochDay == 0L) null else LocalDate.ofEpochDay(epochDay) + } + + private fun getNewDate( + currentDate: LocalDate, + widgetId: Int, + selectedButton: String + ): LocalDate { + return when (selectedButton) { + BUTTON_NEXT -> currentDate.nextSchoolDay + BUTTON_PREV -> currentDate.previousSchoolDay + else -> getWidgetDefaultDateToLoad(widgetId) + } + } + + private fun setWidgetDate(widgetId: Int, dateToSet: LocalDate) { + val widgetDateKey = getDateWidgetKey(widgetId) + sharedPref.putLong(widgetDateKey, dateToSet.toEpochDay(), true) + } + + private fun getWidgetDefaultDateToLoad(widgetId: Int): LocalDate { + val lastLessonEndDateTime = getLastLessonDateTime(widgetId) + + val todayDate = LocalDate.now() + val isLastLessonToday = lastLessonEndDateTime.toLocalDate() == todayDate + val isEndOfLessons = LocalDateTime.now() > lastLessonEndDateTime + + return if (isLastLessonToday && isEndOfLessons) { + todayDate.nextSchoolDay + } else { + todayDate.nextOrSameSchoolDay + } + } + + private fun getLastLessonDateTime(widgetId: Int): LocalDateTime { + val lastLessonTimestamp = sharedPref + .getLong(getTodayLastLessonEndDateTimeWidgetKey(widgetId), 0) + return LocalDateTime.ofEpochSecond(lastLessonTimestamp, 0, ZoneOffset.UTC) + } + + private suspend fun updateWidgetLayout( + context: Context, widgetId: Int ) { - val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) - val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) - val resetNavIntent = - createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) - val adapterIntent = Intent(context, TimetableWidgetService::class.java).apply { - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - action = appWidgetId.toString() //make Intent unique - } - val appIntent = PendingIntent.getActivity( - context, - TIMETABLE_PENDING_INTENT_ID, - SplashActivity.getStartIntent(context, Destination.Timetable()), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - ) + val widgetRemoteViews = RemoteViews(context.packageName, R.layout.widget_timetable) + // Apply the click action intent + val appIntent = createPendingAppIntent(context) + widgetRemoteViews.setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) + + // Display saved date + val date = getSavedWidgetDate(widgetId) ?: getWidgetDefaultDateToLoad(widgetId) val formattedDate = date.toFormattedString("EEE, dd.MM").capitalise() - val remoteView = RemoteViews(context.packageName, R.layout.widget_timetable).apply { - setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) - setTextViewText(R.id.timetableWidgetDate, formattedDate) - setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) + widgetRemoteViews.setTextViewText(R.id.timetableWidgetDate, formattedDate) + + // Apply intents to the date switcher buttons + val nextNavIntent = createNavButtonIntent(context, widgetId, widgetId, BUTTON_NEXT) + val prevNavIntent = createNavButtonIntent(context, -widgetId, widgetId, BUTTON_PREV) + val resetNavIntent = + createNavButtonIntent(context, Int.MAX_VALUE - widgetId, widgetId, BUTTON_RESET) + widgetRemoteViews.run { setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent) - setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) } - student?.let { - setupAccountView(context, student, remoteView, appWidgetId) + // Setup the lesson list adapter + val lessonListAdapterIntent = createLessonListAdapterIntent(context, widgetId) + // --- Ensure the selected date is stored in the shared preferences, + // --- on which the TimetableWidgetFactory relies + setWidgetDate(widgetId, date) + // --- + widgetRemoteViews.apply { + setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) + setRemoteAdapter(R.id.timetableWidgetList, lessonListAdapterIntent) } - with(sharedPref) { - putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) + // Setup profile picture + getWidgetStudent(widgetId)?.let { student -> + setupAccountView(context, student, widgetRemoteViews, widgetId) } + // Apply updates with(appWidgetManager) { - partiallyUpdateAppWidget(appWidgetId, remoteView) - notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) + partiallyUpdateAppWidget(widgetId, widgetRemoteViews) + notifyAppWidgetViewDataChanged(widgetId, R.id.timetableWidgetList) } Timber.d("TimetableWidgetProvider updated") } - private fun createNavIntent( + private fun createPendingAppIntent(context: Context) = PendingIntent.getActivity( + context, TIMETABLE_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.Timetable()), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + private fun createNavButtonIntent( context: Context, code: Int, appWidgetId: Int, buttonType: String ) = PendingIntent.getBroadcast( context, code, Intent(context, TimetableWidgetProvider::class.java).apply { @@ -189,6 +231,17 @@ class TimetableWidgetProvider : BroadcastReceiver() { }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) + private fun createLessonListAdapterIntent(context: Context, widgetId: Int) = + Intent(context, TimetableWidgetService::class.java).apply { + putExtra(EXTRA_APPWIDGET_ID, widgetId) + action = widgetId.toString() //make Intent unique + } + + private suspend fun getWidgetStudent(widgetId: Int): Student? { + val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) + return getStudent(studentId, widgetId) + } + private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { val students = studentRepository.getSavedStudents(false) val student = students.singleOrNull { it.student.id == studentId }?.student @@ -199,6 +252,7 @@ class TimetableWidgetProvider : BroadcastReceiver() { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) } } + else -> null } } catch (e: Exception) { @@ -208,60 +262,64 @@ class TimetableWidgetProvider : BroadcastReceiver() { null } - private fun getWidgetDefaultDateToLoad(appWidgetId: Int): LocalDate { - val lastLessonEndTimestamp = - sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0) - val lastLessonEndDateTime = - LocalDateTime.ofEpochSecond(lastLessonEndTimestamp, 0, ZoneOffset.UTC) + private fun setupAccountView( + context: Context, student: Student, remoteViews: RemoteViews, widgetId: Int + ) { + val accountInitials = getAccountInitials(student.nickOrName) + val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId) - val todayDate = LocalDate.now() - val isLastLessonEndDateNow = lastLessonEndDateTime.toLocalDate() == todayDate - val isLastLessonEndDateAfterNowTime = LocalDateTime.now() > lastLessonEndDateTime + getAvatarBackgroundBitmap(context, student.avatarColor)?.let { + remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, it) + } - return if (isLastLessonEndDateNow && isLastLessonEndDateAfterNowTime) { - todayDate.nextSchoolDay - } else { - todayDate.nextOrSameSchoolDay + remoteViews.apply { + setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials) + setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerPendingIntent) } } - private fun setupAccountView( - context: Context, - student: Student, - remoteViews: RemoteViews, - appWidgetId: Int - ) { - val accountInitials = student.nickOrName - .split(" ") - .mapNotNull { it.firstOrNull() }.take(2) - .joinToString(separator = "").uppercase() + private fun getAccountInitials(name: String): String { + val firstLetters = name.split(" ").mapNotNull { it.firstOrNull() } + return firstLetters.joinToString(separator = "").uppercase() + } - val accountPickerIntent = PendingIntent.getActivity( + private fun createAccountPickerPendingIntent(context: Context, widgetId: Int) = + PendingIntent.getActivity( context, - -Int.MAX_VALUE + appWidgetId, + -Int.MAX_VALUE + widgetId, Intent(context, TimetableWidgetConfigureActivity::class.java).apply { addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + putExtra(EXTRA_APPWIDGET_ID, widgetId) putExtra(EXTRA_FROM_PROVIDER, true) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) - // Create background bitmap + private fun getAvatarBackgroundBitmap(context: Context, avatarColor: Long): Bitmap? { val avatarDrawableResource = R.drawable.background_timetable_widget_avatar - AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable -> + return AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable -> val screenDensity = context.resources.displayMetrics.density val avatarSize = (48 * screenDensity).toInt() - val backgroundBitmap = DrawableCompat.wrap(drawable).run { - DrawableCompat.setTint(this, student.avatarColor.toInt()) + DrawableCompat.wrap(drawable).run { + DrawableCompat.setTint(this, avatarColor.toInt()) toBitmap(avatarSize, avatarSize) } - remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, backgroundBitmap) } + } - remoteViews.apply { - setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials) - setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerIntent) + private fun onWidgetDeleted(intent: Intent) { + val deletedWidgetId = intent.getWidgetId() + deleteWidgetPreferences(deletedWidgetId) + } + + private fun Intent.getWidgetId(): Int { + return getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID) + } + + private fun deleteWidgetPreferences(widgetId: Int) { + with(sharedPref) { + delete(getStudentWidgetKey(widgetId)) + delete(getDateWidgetKey(widgetId)) } } } diff --git a/app/src/main/res/drawable/background_timetable_widget_avatar.xml b/app/src/main/res/drawable/background_timetable_widget_avatar.xml index 7f64c4eb..48298d67 100644 --- a/app/src/main/res/drawable/background_timetable_widget_avatar.xml +++ b/app/src/main/res/drawable/background_timetable_widget_avatar.xml @@ -2,5 +2,5 @@ - + diff --git a/app/src/main/res/drawable/background_widget_item_timetable.xml b/app/src/main/res/drawable/background_widget_item_timetable.xml index 09635758..510c70c0 100644 --- a/app/src/main/res/drawable/background_widget_item_timetable.xml +++ b/app/src/main/res/drawable/background_widget_item_timetable.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_timetable_widget_swap.xml b/app/src/main/res/drawable/ic_timetable_widget_swap.xml index 2f91489a..09c50b4f 100644 --- a/app/src/main/res/drawable/ic_timetable_widget_swap.xml +++ b/app/src/main/res/drawable/ic_timetable_widget_swap.xml @@ -1,6 +1,6 @@ 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 literal 21111 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 bi-Ccg@uSalw&_c=6hVAXS3j3^P6 @@ -16,23 +16,47 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" - android:orientation="horizontal" - android:paddingVertical="16dp"> + android:paddingTop="12dp" + android:paddingBottom="8dp"> + + + + + + + + - + android:paddingBottom="12dp"> - + android:background="@drawable/background_widget_item_timetable" + android:backgroundTint="?attr/colorSurface" + android:gravity="center_vertical" + android:minHeight="48dp" + android:orientation="horizontal" + android:paddingHorizontal="12dp" + android:paddingVertical="8dp"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + android:background="@drawable/background_widget_item_timetable" + android:backgroundTint="?attr/colorSurface" + android:gravity="center_vertical" + android:minHeight="48dp" + android:orientation="horizontal" + android:paddingHorizontal="12dp" + android:paddingVertical="8dp"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_height="wrap_content" + android:background="@drawable/background_widget_item_timetable" + android:backgroundTint="?attr/colorSurface" + android:gravity="center_vertical" + android:minHeight="48dp" + android:orientation="horizontal" + android:paddingHorizontal="12dp" + android:paddingVertical="8dp"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_widget_timetable.xml b/app/src/main/res/layout/item_widget_timetable.xml index 27c9db66..01f4525e 100644 --- a/app/src/main/res/layout/item_widget_timetable.xml +++ b/app/src/main/res/layout/item_widget_timetable.xml @@ -6,11 +6,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/background_widget_item_timetable" - android:backgroundTint="?attr/backgroundColor" + android:backgroundTint="?attr/colorSurface" android:gravity="center_vertical" + android:minHeight="48dp" android:orientation="horizontal" - android:paddingHorizontal="16dp" - android:paddingVertical="12dp" + android:paddingHorizontal="12dp" + android:paddingVertical="8dp" android:theme="@style/Wulkanowy.Widget.Theme" tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory"> @@ -18,15 +19,14 @@ android:id="@+id/timetableWidgetItemNumber" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?attr/textAppearanceHeadline6" - android:textSize="24sp" + android:textSize="22sp" tools:text="1" tools:textColor="?attr/colorTimetableChange" /> @@ -41,7 +41,7 @@ android:id="@+id/timetableWidgetItemTimeFinish" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="4dp" + android:layout_marginTop="2dp" android:textAppearance="?attr/textAppearanceBodySmall" tools:text="09:45" /> @@ -60,7 +60,7 @@ android:layout_height="wrap_content" android:ellipsize="end" android:lines="1" - android:textAppearance="?attr/textAppearanceTitleMedium" + android:textSize="14sp" tools:text="Programowanie aplikacji mobilnych i desktopowych" /> + tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider" + tools:targetApi="s"> + android:paddingTop="12dp" + android:paddingBottom="8dp"> + + + + + + + + + android:textSize="18sp" + tools:text="Friday, 19.05" /> - - - - - - @@ -111,5 +114,7 @@ android:text="@string/widget_timetable_no_items" android:textAppearance="?attr/textAppearanceBody1" android:visibility="gone" /> + + diff --git a/app/src/main/res/xml/provider_widget_timetable.xml b/app/src/main/res/xml/provider_widget_timetable.xml index 3cdad0c8..555d8cb1 100644 --- a/app/src/main/res/xml/provider_widget_timetable.xml +++ b/app/src/main/res/xml/provider_widget_timetable.xml @@ -3,15 +3,15 @@ xmlns:tools="http://schemas.android.com/tools" android:configure="io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity" android:initialLayout="@layout/widget_timetable" - android:minWidth="250dp" - android:minHeight="110dp" - android:minResizeWidth="250dp" - android:minResizeHeight="110dp" + android:minWidth="245dp" + android:minHeight="102dp" + android:minResizeWidth="245dp" + android:minResizeHeight="102dp" android:previewImage="@drawable/img_timetable_widget_preview" android:previewLayout="@layout/widget_timetable_preview" android:resizeMode="horizontal|vertical" android:targetCellWidth="3" - android:targetCellHeight="2" + android:targetCellHeight="3" android:updatePeriodMillis="3600000" android:widgetCategory="home_screen" tools:targetApi="s" /> From 91d7ee442edc87b045ae2693874d612bf0bcfe87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 26 Jul 2023 19:37:06 +0200 Subject: [PATCH 57/74] New Crowdin updates (#2257) --- .../res/values-it-rIT/preferences_values.xml | 65 ++ app/src/main/res/values-it-rIT/strings.xml | 747 ++++++++++++++++++ app/src/main/res/values-uk/strings.xml | 6 +- 3 files changed, 815 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/values-it-rIT/preferences_values.xml create mode 100644 app/src/main/res/values-it-rIT/strings.xml diff --git a/app/src/main/res/values-it-rIT/preferences_values.xml b/app/src/main/res/values-it-rIT/preferences_values.xml new file mode 100644 index 00000000..ac2b6e9e --- /dev/null +++ b/app/src/main/res/values-it-rIT/preferences_values.xml @@ -0,0 +1,65 @@ + + + + Light + Dark + Black (AMOLED) + + + System language + Polski + English + Pусский + Українська + Deutsch + Čeština + Slovenčina + + + 15 minutes + 30 minutes + 1 hour + 2 hours + 6 hours + 12 hours + 24 hours + + + 0,00 + 0,25 + 0,33 + 0,5 + 0,75 + + + Alphabetically + By date + By average + + + Dzienniczek+ + Wulkanowy + Grade colors in register + + + Up to 1 at once + Always expanded + Unlimited expansions + + + Average of grades only from selected semester + Average of averages from both semesters + Average of grades from the whole year + + + Lucky number + Unread messages + Attendance + Lessons + Grades + Homework + School announcements + Exams + Conferences + + diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml new file mode 100644 index 00000000..5c7d02a0 --- /dev/null +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -0,0 +1,747 @@ + + + + Login + Wulkanowy + Grades + Attendance + Exams + Timetable + Settings + More + About + Log viewer + Debug + Notification debug + Contributors + Licenses + Messages + New message + New homework + Notes and achievements + Homework + Accounts manager + Select account + Account details + Student info + Dashboard + Notifications center + Menu configuartion + + Semester %1$d, %2$d/%3$d + + Sign in with the student or parent account + Enter the symbol from the register page for account: <b>%1$s</b> + Username + Email + Login, PESEL or e-mail + Password + UONET+ register variant + Custom domain suffix + Mobile API + Scraper + Hybrid + Token + PIN + Symbol + Sign in + Password too short + Login details are incorrect + %1$s. Make sure the correct UONET+ register variation is selected below + Invalid PIN + Invalid token + Token expired + Invalid email + Use the assigned login instead of email + Use the assigned login or email in @%1$s + Invalid symbol + Student not found. Validate the symbol and the chosen variation of the UONET+ register + Selected student is already logged in + The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the first login screen + Select students to log in to the application + Other options + In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices + This mode displays the same data as it appears on the register website + The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase + Privacy policy + Trouble signing in? Contact us! + Email + Discord + Send email + Make sure you select the correct UONET+ register variation! + I forgot my password + Recover your account + Recover + Student is already signed in + Standard + Other search locations + No active students found + Enter a different symbol + + Enable notifications + Enable notifications so you don\'t miss message from teacher or new grade + Skip + Enable + + Account manager + Log in + Session expired + Session expired, log in again + Application support + Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time + Enable ads + + Grade + Semester %d + Change semester + No grades + Weight + Weight: %s + Comment + Number of new ratings: %1$d + Average: %1$.2f + Points: %s + No average + Total points + Final grade + Predicted grade + Calculated average + How does Calculated Average work? + The Calculated Average is the arithmetic average calculated from the subjects averages. It allows you to know the approximate final average. It is calculated in a way selected by the user in the application settings. It is recommended that you choose the appropriate option. This is because the calculation of school averages differs. Additionally, if your school reports the average of the subjects on the Vulcan page, the application downloads them and does not calculate these averages. This can be changed by forcing the calculation of the average in the application settings.\n\nAverage of grades only from selected semester:\n1. Calculating the weighted average for each subject in a given semester\n2.Adding calculated averages\n3. Calculation of the arithmetic average of the summed averages\n\nAverage of averages from both semesters:\n1.Calculating the weighted average for each subject in semester 1 and 2\n2. Calculating the arithmetic average of the calculated averages for semesters 1 and 2 for each subject.\n3. Adding calculated averages\n4. Calculation of the arithmetic average of the summed averages\n\nAverage of grades from the whole year:\n1. Calculating weighted average over the year for each subject. The final average in the 1st semester is irrelevant.\n2. Adding calculated averages\n3. Calculating the arithmetic average of summed averages + How does the Final Average work? + The Final Average is the arithmetic average calculated from all currently available final grades in the given semester.\n\nThe calculation scheme consists of the following steps:\n1. Summing up the final grades given by teachers\n2. Divide by the number of subjects that have already been graded + Final average + from %1$d of %2$d subjects + Summary + Class + Mark as read + Partial + Semester + Points + Legend + Class average: %1$s + Your average: %1$s + Your grade: %1$s + Class + Student + + %d grade + %d grades + + + New grade + New grades + + + New predicted grade + New predicted grades + + + New final grade + New final grades + + + You received %1$d grade + You received %1$d grades + + + You received %1$d predicted grade + You received %1$d predicted grades + + + You received %1$d final grade + You received %1$d final grades + + + Lesson + Room + Group + Hours + Changes + No lessons this day + %s min + %s sec + %1$s left + in %1$s + Finished + Now: %s + Next: %s + Later: %s + %1$s lesson %2$d - %3$s + Change of room from %1$s to %2$s + Change of teacher from %1$s to %2$s + Change of subject from %1$s to %2$s + + Timetable change + Timetable changes + + + %1$s - %2$d change in timetable + %1$s - %2$d changes in timetable + + + %1$d change in timetable + %1$d changes in timetable + + + %d change + %d changes + + + Completed lessons + Show completed lessons + No info about completed lessons + Topic + Absence + Resources + + Additional lessons + Show additional lessons + No info about additional lessons + New lesson + New additional lesson + Additional lesson added successfully + Additional lesson deleted successfully + Repeat weekly + Delete additional lesson + Just this lesson + All in the series + Start time + End time + End time must be greater than start time + + Attendance summary + Absent for school reasons + Excused absence + Unexcused absence + Exemption + Excused lateness + Unexcused lateness + Present + Deleted + Unknown + Number of lesson + No entries + Absence reason (optional) + Send + Absence excuse request sent successfully! + You must select at least one absence! + Excuse + + New attendance + New attendance + + + %1$d new attendance + %1$d attendance + + + %d attendance + %d attendance + + + Total + + No exams this week + Type + Entry date + + New exam + New exams + + + %d new exam + %d new exams + + + %d exam + %d exams + + + Inbox + Sent + Trash + (no subject) + No messages + From: + To: + Date: %1$s + Reply + Forward + Select all + Unselect all + Move to trash + Delete permanently + Message deleted successfully + student + parent + guardian + employee + Share + Print + Subject + Content + Message sent successfully + Message does not exist + You need to choose at least 1 recipient + The message content must be at least 3 characters + All mailboxes + Only unread + Only with attachments + Read: %s + Read by: %1$d of %2$d people + + %1$d message + %1$d messages + + + New message + New messages + + Do you want to restore draft message? + Do you want to restore draft message with recipients: %s? + + You received %1$d message + You received %1$d messages + + + %1$d selected + %1$d selected + + Messages deleted + Choose mailbox + + No info about notes + Points + + %d note + %d notes + + + New note + New notes + + + You received %1$d note + You received %1$d notes + + + + %d praise + %d praises + + + New praise + New praises + + + You received %1$d praise + You received %1$d praises + + + + %d neutral note + %d neutral notes + + + New neutral note + New neutral notes + + + You received %1$d neutral note + You received %1$d neutral notes + + + No info about homework + Mark as done + Mark as undone + Add homework + Homework added successfully + Homework deleted successfully + Attachments + + New homework + New homework + + + You received %d new homework + You received %d new homework + + + %d homework + %d homework + + + Lucky number + Today\'s lucky number is + No info about the lucky number + Lucky number for today + Today\'s lucky number is: %s + Show history + + Lucky number history + No info about lucky numbers + + Mobile devices + No devices + Deregister + Device removed + QR code + Token + Symbol + PIN + + School and teachers + + School + No info about school + School name + School address + Telephone + Name of headmaster + Name of pedagogue + Show on map + Call + + Teachers + No info about teachers + No subject + + Conferences + No info about conferences + + %d conference + %d conferences + + + New conference + New conferences + + + You have %1$d new conference + You have %1$d new conferences + + Present at conference + Agenda + Place + Topic + + School announcements + No school announcements + + %d school announcement + %d school announcements + + + New school announcement + New school announcements + + + You have %1$d new school announcement + You have %1$d new school announcements + + + Add account + Logout + Do you want to log out this student? + Student logout + Student account + Parent account + Edit data + Accounts manager + Select student + Family + Contact + Residence details + Personal information + + App version + Contributors + List of Wulkanowy developers + Report a bug + Send a bug report via e-mail + FAQ + Read Frequently Asked Questions + Discord server + Join the Wulkanowy community + Facebook fanpage + Twitter page + Follow us on twitter + Like our facebook fanpage + Privacy policy + Rules for collecting personal data + System settings + Open system settings + Homepage + Visit the website and help develop the application + Licenses + Licenses of libraries used in the application + + License + + Avatar + See more on GitHub + + No info about student or student family + Name + Second name + Gender + Polish citizenship + Family name + Mother\'s and father\'s names + Phone + Cellphone + E-mail + Address of residence + Address of registration + Correspondence address + Surname and first name + Degree of kinship + Address + Phones + Male + Female + Last name + Guardian + + Nick + Add nick + Choose avatar color + + Share logs + Refresh + + Lessons + (Tomorrow) + (Today and tomorrow) + In a moment: + Soon: + First: + Now: + End of lessons + Next: + Later: + + %1$d more lesson + %1$d more lessons + + until %1$s + No upcoming lessons + An error occurred while loading the lessons + Homework + No homework to do + An error occurred while loading the homework + + %1$d more homework + %1$d more homework + + due %1$s + Last grades + No new grades + An error occurred while loading the grades + School announcements + No current announcements + An error occurred while loading the announcements + + %1$d more announcement + %1$d more announcements + + Exams + No upcoming exams + An error occurred while loading the exams + + %1$d more exam + %1$d more exams + + Conferences + No upcoming conferences + An error occurred while loading the conferences + + %1$d more conference + %1$d more conferences + + An error occurred while loading data + None + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available + + Content + Retry + Description + No description + Teacher + Date + Entry date + Color + Details + Category + Close + No data + Subject + Prev + Next + Search + Search… + Yes + No + Save + Title + Add + Copied + Undo + Change + Add to calendar + Cancel + + No lessons + Synchronized on %1$s at %2$s + Choose theme + Light + Dark + System Theme + + App + Default view + Calculated average options + Force average calculation by app + Show presence + Theme + Grades expanding + Mark current lesson + Show groups next to subjects + Show chart list in class grades + Show subjects without grades + Grades color scheme + Subjects sorting + Language + Menu configuration + Set the order of functions in the menu + Notifications + Other + Show notifications + Show upcoming lesson notifications + Make upcoming lesson notification persistent + Turn off when notification is not showing in your watch/band + Open system notification settings + Fix synchronization & notifications issues + Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. + Show debug notifications + Synchronization is disabled + Official app notifications + Capture official app notifications + Remove official app notifications after capture + Capture notifications + With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY + Upcoming lesson notifications + You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature. + Go to settings + Synchronization + Automatic update + Suspended on holidays + Updates interval + Wi-Fi only + Sync now + Synced! + Sync failed + Sync in progress + Last full sync: %s + Value of the plus + Value of the minus + Reply with message history + Show arithmetic average when no weights provided + Support + Privacy Policy + Agreements + Consent to processing of data related to ads + Show ads in app + Watch single ad to support project + Consent to data processing + To view an advertisement you must agree to the data processing terms of our Privacy Policy + Agree + Privacy policy + Ad is loading + Thank you for your support, come back later for more ads + Can we use your data to display ads? + You can change your choice anytime in the app settings. We may use your data to display ads tailored to you or, using less of your data, display non-personalized ads. Please see our Privacy Policy for details + Personalized ads + Non-personalized ads + I am over 18 years old + Yes, personalized ads + Yes, non-personalized ads + Advanced + Appearance & Behavior + Notifications + Synchronization + Advertisements + Grades + Dashboard + Tiles visibility + Attendance + Timetable + Grades + Calculated average + Messages + Appearance & Behavior + Languages, themes, subjects sorting + App notifications, fix problems + Notifications + Synchronization + Automatic update, synchronization interval + Plus and minus values, average calculation + Advanced + App version, contributors, social portals + Displaying advertisements, project support + + New grades + New homework + New conferences + New exams + Lucky number + New messages + New notes + New school announcements + Push notifications + Upcoming lessons + Debug + Timetable change + New attendance + + Black + Red + Blue + Green + Purple + No color + + Download of updates has started… + An update has just been downloaded. + Restart + Update failed! Wulkanowy may not function properly. Consider updating + + Application restart + The application must restart for the changes to be saved + Restart + + Authorization has been rejected. The data provided does not match the records in the secretary\'s office. + Invalid PESEL + PESEL + Authorize + Authorization completed successfully + Authorization + To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below + Skip for now + + No internet connection + An error occurred. Check your device clock + Connection to register failed. Servers can be overloaded. Please try again later + Loading data failed. Please try again later + Register password change required + Maintenance underway UONET + register. Try again later + Unknown UONET + register error. Try again later + Unknown application error. Please try again later + An unexpected error occurred + Feature disabled by your school + Feature not available. Login in a mode other than Mobile API + This field is required + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 30a587cc..f89e38fc 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -37,7 +37,7 @@ Логін, PESEL або e-mail Пароль Тип щоденника UONET+ - Custom domain suffix + Користувацький суфікс домену Мobile API Scraper Hybrid @@ -814,8 +814,8 @@ Авторизацію відхилено. Надані дані не збігаються із записами в кабінеті секретаря. Неправильний PESEL Число PESEL - Authorize - Authorization completed successfully + Авторизовать + Авторизація пройшла успішно Авторизувати Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче Поки що пропустити From 64cc24ae6012470b59c9e7038c2145147f09c29a Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Wed, 26 Jul 2023 22:17:58 +0200 Subject: [PATCH 58/74] Add incognito mode in messages (#1970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../data/repositories/MessageRepository.kt | 22 ++++++++++++++----- .../repositories/PreferencesRepository.kt | 6 +++++ .../ui/modules/message/MessageFragment.kt | 9 +++++++- .../ui/modules/message/MessagePresenter.kt | 13 ++++++++++- .../ui/modules/message/MessageView.kt | 3 +++ .../message/preview/MessagePreviewFragment.kt | 5 +++++ .../preview/MessagePreviewPresenter.kt | 13 ++++++++++- .../message/preview/MessagePreviewView.kt | 3 +++ .../modules/message/tab/MessageTabFragment.kt | 17 +++++++++----- .../message/tab/MessageTabPresenter.kt | 5 +++-- .../ui/modules/message/tab/MessageTabView.kt | 3 ++- .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 5 +++++ .../res/xml/scheme_preferences_advanced.xml | 7 ++++++ 15 files changed, 96 insertions(+), 17 deletions(-) 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 53d9bead..c8fccb23 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 @@ -3,18 +3,26 @@ package io.github.wulkanowy.data.repositories import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.entities.* +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.pojos.MessageDraft +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder @@ -25,7 +33,6 @@ import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import timber.log.Timber @@ -97,7 +104,7 @@ class MessageRepository @Inject constructor( shouldFetch = { checkNotNull(it) { "This message no longer exist!" } Timber.d("Message content in db empty: ${it.message.content.isBlank()}") - it.message.unread || it.message.content.isBlank() + (it.message.unread && markAsRead) || it.message.content.isBlank() }, query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) @@ -113,7 +120,10 @@ class MessageRepository @Inject constructor( messagesDb.updateAll( listOf(old.message.apply { id = message.id - unread = !markAsRead + unread = when { + markAsRead -> false + else -> unread + } sender = new.sender recipients = new.recipients.singleOrNull() ?: "Wielu adresatów" content = content.ifBlank { new.content } @@ -123,7 +133,7 @@ class MessageRepository @Inject constructor( items = new.attachments.mapToEntities(message.messageGlobalKey), ) - Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read") + Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read: $markAsRead") } ) 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 348a4054..1b489340 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 @@ -343,6 +343,12 @@ class PreferencesRepository @Inject constructor( ) } + var isIncognitoMode: Boolean + get() = getBoolean(R.string.pref_key_incognito_moge, R.bool.pref_default_incognito_mode) + set(value) = sharedPref.edit { + putBoolean(context.getString(R.string.pref_key_incognito_moge), value) + } + var installationId: String get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } 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 4317fb7f..02bc13a1 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 @@ -11,7 +11,9 @@ import androidx.core.view.updateMargins import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.enums.MessageFolder.* +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.enums.MessageFolder.SENT +import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter @@ -49,6 +51,7 @@ class MessageFragment : BaseFragment(R.layout.fragment_m override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentMessageBinding.bind(view) + messageContainer = binding.messageViewPager presenter.onAttachView(this) } @@ -95,6 +98,10 @@ class MessageFragment : BaseFragment(R.layout.fragment_m binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE } + override fun showMessage(messageId: Int) { + showMessage(getString(messageId)) + } + override fun showNewMessage(show: Boolean) { binding.openSendMessageButton.run { if (show) show() else hide() 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 cf6bad19..37a2d422 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 @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.modules.message +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -9,7 +11,8 @@ import javax.inject.Inject class MessagePresenter @Inject constructor( errorHandler: ErrorHandler, - studentRepository: StudentRepository + studentRepository: StudentRepository, + private val preferencesRepository: PreferencesRepository, ) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: MessageView) { @@ -19,6 +22,14 @@ class MessagePresenter @Inject constructor( Timber.i("Message view was initialized") loadData() } + + showIncognitoModeReminderMessage() + } + + private fun showIncognitoModeReminderMessage() { + if (preferencesRepository.isIncognitoMode) { + view?.showMessage(R.string.message_incognito_mode_on) + } } fun onPageSelected(index: Int) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt index def4a275..7fdc6e18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message +import androidx.annotation.StringRes import io.github.wulkanowy.ui.base.BaseView interface MessageView : BaseView { @@ -12,6 +13,8 @@ interface MessageView : BaseView { fun showProgress(show: Boolean) + fun showMessage(@StringRes messageId: Int) + fun showNewMessage(show: Boolean) fun showTabLayout(show: Boolean) 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 6c54d9fc..3ed685cd 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 @@ -12,6 +12,7 @@ import android.view.View.VISIBLE import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient +import androidx.annotation.StringRes import androidx.core.content.getSystemService import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager @@ -164,6 +165,10 @@ class MessagePreviewFragment : binding.messagePreviewErrorRetry.setOnClickListener { callback() } } + override fun showMessage(@StringRes messageId: Int) { + showMessage(getString(messageId)) + } + override fun openMessageReply(message: Message?) { context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message, true)) } } 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 56f23b6f..cd7b7284 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 @@ -2,11 +2,13 @@ package io.github.wulkanowy.ui.modules.message.preview import android.annotation.SuppressLint import androidx.core.text.parseAsHtml +import io.github.wulkanowy.R import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler @@ -20,6 +22,7 @@ class MessagePreviewPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, + private val preferencesRepository: PreferencesRepository, private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { @@ -54,7 +57,11 @@ class MessagePreviewPresenter @Inject constructor( private fun loadData(messageToLoad: Message) { flatResourceFlow { val student = studentRepository.getCurrentStudent() - messageRepository.getMessage(student, messageToLoad, true) + messageRepository.getMessage( + student = student, + message = messageToLoad, + markAsRead = !preferencesRepository.isIncognitoMode, + ) } .logResourceStatus("message ${messageToLoad.messageId} preview") .onResourceData { @@ -65,6 +72,10 @@ class MessagePreviewPresenter @Inject constructor( setMessageWithAttachment(it) showContent(true) initOptions() + + if (preferencesRepository.isIncognitoMode && it.message.unread) { + showMessage(R.string.message_incognito_description) + } } } else { view?.run { 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 c5a94793..7f5f140b 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,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.preview +import androidx.annotation.StringRes import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.ui.base.BaseView @@ -43,4 +44,6 @@ interface MessagePreviewView : BaseView { fun popView() fun printDocument(html: String, jobName: String) + + fun showMessage(@StringRes messageId: Int) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 592cbd60..4364e868 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import android.view.View import android.view.View.* import android.widget.CompoundButton +import androidx.annotation.StringRes import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.core.os.bundleOf @@ -134,14 +135,20 @@ class MessageTabFragment : BaseFragment(R.layout.frag } } + @Deprecated("Deprecated in Java") @Suppress("DEPRECATION") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.action_menu_message_tab, menu) - val searchView = menu.findItem(R.id.action_search).actionView as SearchView - searchView.queryHint = getString(R.string.all_search_hint) - searchView.maxWidth = Int.MAX_VALUE + initializeSearchView(menu) + } + + private fun initializeSearchView(menu: Menu) { + val searchView = (menu.findItem(R.id.action_search).actionView as SearchView).apply { + queryHint = getString(R.string.all_search_hint) + maxWidth = Int.MAX_VALUE + } searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String) = false override fun onQueryTextChange(query: String): Boolean { @@ -207,8 +214,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag binding.messageTabSwipe.isRefreshing = show } - override fun showMessagesDeleted() { - showMessage(getString(R.string.message_messages_deleted)) + override fun showMessage(@StringRes messageId: Int) { + showMessage(getString(messageId)) } override fun notifyParentShowNewMessage(show: Boolean) { 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 ec92e9c2..90f93b14 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import io.github.wulkanowy.R import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message @@ -26,7 +27,7 @@ class MessageTabPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val analytics: AnalyticsHelper + private val analytics: AnalyticsHelper, ) : BasePresenter(errorHandler, studentRepository) { lateinit var folder: MessageFolder @@ -135,7 +136,7 @@ class MessageTabPresenter @Inject constructor( messageRepository.deleteMessages(student, selectedMailbox, messageList) } .onFailure(errorHandler::dispatch) - .onSuccess { view?.showMessagesDeleted() } + .onSuccess { view?.showMessage(R.string.message_messages_deleted) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt index 6ece6621..247af434 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import androidx.annotation.StringRes import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.ui.base.BaseView @@ -26,7 +27,7 @@ interface MessageTabView : BaseView { fun showEmpty(show: Boolean) - fun showMessagesDeleted() + fun showMessage(@StringRes messageId: Int) fun showErrorView(show: Boolean) diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 6c81100d..fefd9b13 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -38,4 +38,5 @@ false false + false diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 716639c0..e7fa542a 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -39,5 +39,6 @@ ads_privacy_policy ads_consent_data_processing ads_over_eighteen + incognito_mode appearance_menu_order diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98c316cb..9dc7e796 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -339,6 +339,8 @@ Messages deleted Choose mailbox + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message @@ -728,6 +730,9 @@ Value of the minus Reply with message history Show arithmetic average when no weights provided + Incognito mode + Do not inform about reading the message + Support Privacy Policy Agreements diff --git a/app/src/main/res/xml/scheme_preferences_advanced.xml b/app/src/main/res/xml/scheme_preferences_advanced.xml index 95f6f383..8185de81 100644 --- a/app/src/main/res/xml/scheme_preferences_advanced.xml +++ b/app/src/main/res/xml/scheme_preferences_advanced.xml @@ -53,5 +53,12 @@ app:key="@string/pref_key_fill_message_content" app:singleLineTitle="false" app:title="@string/pref_other_fill_message_content" /> + From 7f6a13a9ee8cae095855b9798b4220b5bd127299 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:35:29 +0000 Subject: [PATCH 59/74] Bump coroutines from 1.7.2 to 1.7.3 (#2267) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f8603cc8..e188d709 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ ext { room = "2.5.2" chucker = "3.5.2" mockk = "1.13.5" - coroutines = "1.7.2" + coroutines = "1.7.3" } dependencies { From fc2adff997dd24f2fc75d3cf7bcc60a564bf3716 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:35:38 +0000 Subject: [PATCH 60/74] Bump androidx.fragment:fragment-ktx from 1.6.0 to 1.6.1 (#2269) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e188d709..d43b667c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.7.2" implementation "androidx.appcompat:appcompat:1.6.1" - implementation "androidx.fragment:fragment-ktx:1.6.0" + implementation "androidx.fragment:fragment-ktx:1.6.1" implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" From 0f129109ba25cace9a0e09cc4ed7d26668daa5ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 19:00:17 +0000 Subject: [PATCH 61/74] Bump com.android.tools.build:gradle from 8.0.2 to 8.1.0 (#2266) --- app/build.gradle | 26 ++++++++++++------------ build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d43b667c..74ef02bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,6 @@ +import com.github.triplet.gradle.androidpublisher.ReleaseStatus +import ru.cian.huawei.publish.ReleaseNote + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlinx-serialization' @@ -17,7 +20,7 @@ apply from: 'hooks.gradle' android { namespace 'io.github.wulkanowy' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "io.github.wulkanowy" @@ -78,7 +81,7 @@ android { } } - flavorDimensions "platform" + flavorDimensions += "platform" productFlavors { hms { @@ -117,20 +120,20 @@ android { } } - testOptions.unitTests { - includeAndroidResources = true + testOptions { + unitTests.includeAndroidResources = true // workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750 - all { jvmArgs '-noverify' } + unitTests.all { jvmArgs '-noverify' } } compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "11" + jvmTarget = "17" freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] } @@ -152,14 +155,11 @@ kapt { ksp { arg("room.schemaLocation", "$projectDir/schemas".toString()) } -kotlin { - jvmToolchain(11) -} play { defaultToAppBundles = false track = 'production' - releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS + releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.25d updatePriority = 1 enabled.set(false) @@ -172,7 +172,7 @@ huaweiPublish { buildFormat = "aab" deployType = "publish" releaseNotes = [ - new ru.cian.huawei.publish.ReleaseNote( + new ReleaseNote( "pl-PL", "$projectDir/src/main/play/release-notes/pl-PL/default.txt" ) diff --git a/build.gradle b/build.gradle index 9584caac..c5a6f598 100644 --- a/build.gradle +++ b/build.gradle @@ -14,12 +14,12 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.11" - classpath 'com.android.tools.build:gradle:8.0.2' + classpath 'com.android.tools.build:gradle:8.1.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.9.1.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.7' - classpath "com.github.triplet.gradle:play-publisher:3.6.0" + classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.3.0.3225" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" @@ -37,6 +37,6 @@ allprojects { } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8707e8b5..9b0a13f0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 50326c7a48644d6f5997df130d8d88a1496d7188 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 19:00:37 +0000 Subject: [PATCH 62/74] Bump androidx.recyclerview:recyclerview from 1.3.0 to 1.3.1 (#2268) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 74ef02bc..197ea356 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,7 +206,7 @@ dependencies { implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" - implementation "androidx.recyclerview:recyclerview:1.3.0" + implementation "androidx.recyclerview:recyclerview:1.3.1" implementation "androidx.viewpager2:viewpager2:1.1.0-beta02" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" From 74820f9571cb9cf6259f45b73409552277ae975a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 31 Jul 2023 21:32:07 +0200 Subject: [PATCH 63/74] New Crowdin updates (#2265) --- app/src/main/res/values-cs/strings.xml | 6 +++++- app/src/main/res/values-da-rDK/strings.xml | 4 ++++ app/src/main/res/values-de/strings.xml | 4 ++++ app/src/main/res/values-es-rES/strings.xml | 4 ++++ app/src/main/res/values-it-rIT/strings.xml | 4 ++++ app/src/main/res/values-pl/strings.xml | 4 ++++ app/src/main/res/values-ru/strings.xml | 4 ++++ app/src/main/res/values-sk/strings.xml | 6 +++++- app/src/main/res/values-uk/strings.xml | 4 ++++ 9 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 964329da..ff461d42 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -37,7 +37,7 @@ Přihlášení, číslo PESEL nebo e-mail Heslo Variace deníku UONET+ - Custom domain suffix + Vlastní přípona domény Mobile API Scraper Hybridní @@ -352,6 +352,8 @@ Zprávy odstraněné Vyberte poštovní schránku + Anonymní režim je zapnutý + Díky anonymnímu režimu není odesílatel upozorněn, když si zprávu přečtete Žádné informace o poznámkách Body @@ -738,6 +740,8 @@ Hodnota mínusu Odpovědět s historií zpráv Vypočítat aritmetický průměr, pokud žádná známka nemá váhu + Anonymní režim + Neinformovat o přečtení zprávy Podpora Ochrana osobních údajů Souhlasy diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 5c7d02a0..259a4264 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -310,6 +310,8 @@ Messages deleted Choose mailbox + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message No info about notes Points @@ -650,6 +652,8 @@ Value of the minus Reply with message history Show arithmetic average when no weights provided + Incognito mode + Do not inform about reading the message Support Privacy Policy Agreements diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 96423e35..1836d047 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -310,6 +310,8 @@ Nachrichten gelöscht Postfach auswählen + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message Keine Informationen über Eintragen Punkte @@ -650,6 +652,8 @@ Wert des Minus Antwort mit Nachrichtenhistorie Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind + Incognito mode + Do not inform about reading the message Unterstützung Datenschutz-Bestimmungen Vereinbarungen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 5c7d02a0..259a4264 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -310,6 +310,8 @@ Messages deleted Choose mailbox + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message No info about notes Points @@ -650,6 +652,8 @@ Value of the minus Reply with message history Show arithmetic average when no weights provided + Incognito mode + Do not inform about reading the message Support Privacy Policy Agreements diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 5c7d02a0..259a4264 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -310,6 +310,8 @@ Messages deleted Choose mailbox + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message No info about notes Points @@ -650,6 +652,8 @@ Value of the minus Reply with message history Show arithmetic average when no weights provided + Incognito mode + Do not inform about reading the message Support Privacy Policy Agreements diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2c707797..0c1bbf78 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -352,6 +352,8 @@ Wiadomości zostały usunięte Wybierz skrzynkę + Tryb incognito jest włączony + Dzięki trybowi incognito nadawca nie zobaczy, że przeczytałeś tę wiadomość Brak informacji o uwagach Punkty @@ -738,6 +740,8 @@ Wartość minusa Odpowiadaj z historią wiadomości Licz średnią arytmetyczną, gdy żadna ocena nie ma wagi + Tryb incognito + Nie informuj o przeczytaniu wiadomości Wsparcie Polityka prywatności Zgody diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index eb8be002..60697174 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -352,6 +352,8 @@ Сообщение удалено Выбрать почтовый ящик + Incognito mode is on + Thanks to incognito mode sender is not notified when you read the message Нет записей о замечаниях и свершениях Баллы @@ -738,6 +740,8 @@ Стоимость минуса Отвечать с историей сообщений Показывать среднее арифметическое при отсутствии стоимости + Incognito mode + Do not inform about reading the message Поддержка Политика приватности Соглашения diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index bcbd832a..20d8818b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -37,7 +37,7 @@ Prihlásenie, číslo PESEL alebo e-mail Heslo Variácia denníka UONET+ - Custom domain suffix + Vlastná prípona domény Mobile API Scraper Hybridné @@ -352,6 +352,8 @@ Správy odstránené Vyberte poštovú schránku + Režim inkognito je zapnutý + Vďaka inkognito režimu nie je odosielateľ upozornený, keď si správu prečítate Žiadne informácie o poznámkach Body @@ -738,6 +740,8 @@ Hodnota mínusu Odpovedať s históriou správ Vypočítať aritmetický priemer, ak žiadna známka nemá váhu + Režim inkognito + Neinformovať o prečítaní správy Podpora Ochrana osobných údajov Súhlasy diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f89e38fc..db5c3cb0 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -352,6 +352,8 @@ Листи видалено Вибрати поштову скриньку + Режим анонімності включено + Завдяки режиму анонімності, відправник не буде сповіщений коли ви прочитаєте повідомлення Немає інформації о зауваженнях Бали @@ -738,6 +740,8 @@ Вартість мінуса Відповісти з історією повідомлень Вилічити середню аритметичну, якщо оцінка немає вартості + Анонімний режим + Не повідомляти про прочитання повідомлення Підтримка Політика конфіденційності Угоди From 722b4e58126de65c05dcee6c048cd410a7db6db0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:19:33 +0000 Subject: [PATCH 64/74] Bump androidx.preference:preference-ktx from 1.2.0 to 1.2.1 (#2274) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 197ea356..8ff34207 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -205,7 +205,7 @@ dependencies { implementation "androidx.fragment:fragment-ktx:1.6.1" implementation "androidx.annotation:annotation:1.6.0" - implementation "androidx.preference:preference-ktx:1.2.0" + implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.recyclerview:recyclerview:1.3.1" implementation "androidx.viewpager2:viewpager2:1.1.0-beta02" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" From c4396036ce7790fb4c35714393b6235be3c36512 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:19:49 +0000 Subject: [PATCH 65/74] Bump com.google.firebase:firebase-bom from 32.2.0 to 32.2.2 (#2271) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8ff34207..5f148144 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -247,7 +247,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:32.2.0') + playImplementation platform('com.google.firebase:firebase-bom:32.2.2') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From e21c17ea99005a578d47f101b020ce1faa9b9985 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:20:05 +0000 Subject: [PATCH 66/74] Bump com.google.firebase:firebase-crashlytics-gradle from 2.9.7 to 2.9.8 (#2270) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c5a6f598..2b52c068 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ buildscript { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.9.1.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.7' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.8' classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.3.0.3225" From 8fbe341607467ea821e3b58b38193af2fe7fc639 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:20:19 +0000 Subject: [PATCH 67/74] Bump com.huawei.hms:hianalytics from 6.10.0.302 to 6.10.0.303 (#2272) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5f148144..824c8376 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -256,7 +256,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:22.2.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.302' + hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.303' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 7d5a29d4053fecdf7e1b1c6e4cf0d7cdf554789a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:20:45 +0000 Subject: [PATCH 68/74] Bump org.gradle.toolchains.foojay-resolver-convention (#2276) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index af9bb737..16731297 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' } include ':app' From 024ca897084bf1b939afb747ec4be1220d89ace8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:39:04 +0000 Subject: [PATCH 69/74] Bump mockk from 1.13.5 to 1.13.7 (#2275) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 824c8376..37b165c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { android_hilt = "1.0.0" room = "2.5.2" chucker = "3.5.2" - mockk = "1.13.5" + mockk = "1.13.7" coroutines = "1.7.3" } From 533157709b3db6ea03827b75660a1c77761cac7f Mon Sep 17 00:00:00 2001 From: Antoni Paduch <70513486+janAte1@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:47:12 +0200 Subject: [PATCH 70/74] Add option to show empty tiles in the timetable (#2236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/src/main/assets/contributors.json | 4 + .../wulkanowy/data/enums/TimetableGapsMode.kt | 11 ++ .../repositories/PreferencesRepository.kt | 9 +- .../widgets/TimetableWidgetService.kt | 12 +- .../ui/modules/timetable/TimetableAdapter.kt | 47 ++++++-- .../ui/modules/timetable/TimetableItem.kt | 6 + .../modules/timetable/TimetablePresenter.kt | 71 +++++++++--- .../timetablewidget/TimetableWidgetFactory.kt | 107 ++++++++++++++---- .../timetablewidget/TimetableWidgetItem.kt | 26 +++++ .../main/res/layout/item_timetable_empty.xml | 43 +++++++ .../layout/item_widget_timetable_empty.xml | 36 ++++++ .../main/res/values-pl/preferences_values.xml | 5 + app/src/main/res/values-pl/strings.xml | 7 ++ .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + .../main/res/values/preferences_values.xml | 11 ++ app/src/main/res/values/strings.xml | 5 + .../res/xml/scheme_preferences_appearance.xml | 8 ++ 18 files changed, 363 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt create mode 100644 app/src/main/res/layout/item_timetable_empty.xml create mode 100644 app/src/main/res/layout/item_widget_timetable_empty.xml diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index b2849931..a7629c22 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -50,5 +50,9 @@ { "displayName": "Tomasz F.", "githubUsername": "Pengwius" + }, + { + "displayName": "Antoni Paduch", + "githubUsername": "janAte1" } ] diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt new file mode 100644 index 00000000..c8310c02 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/TimetableGapsMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class TimetableGapsMode(val value: String) { + NO_GAPS("no_gaps"), + BETWEEN_LESSONS("between"), + BETWEEN_AND_BEFORE_LESSONS("before_and_between"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: BETWEEN_LESSONS + } +} 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 1b489340..85c74072 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 @@ -15,7 +15,6 @@ import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem 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.time.Instant @@ -201,6 +200,14 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_timetable_show_timers ) + val showTimetableGaps: TimetableGapsMode + get() = TimetableGapsMode.getByValue( + getString( + R.string.pref_key_timetable_show_gaps, + R.string.pref_default_timetable_show_gaps + ) + ) + val showSubjectsWithoutGrades: Boolean get() = getBoolean( R.string.pref_key_subjects_without_grades, diff --git a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt index d48556fa..ffdb07ec 100644 --- a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt +++ b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.widget.RemoteViewsService import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository @@ -26,10 +27,19 @@ class TimetableWidgetService : RemoteViewsService() { @Inject lateinit var sharedPref: SharedPrefProvider + @Inject + lateinit var prefRepository: PreferencesRepository + override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { Timber.d("TimetableWidgetFactory created") return TimetableWidgetFactory( - timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent + timetableRepository = timetableRepo, + studentRepository = studentRepo, + semesterRepository = semesterRepo, + sharedPref = sharedPref, + prefRepository = prefRepository, + context = applicationContext, + intent = intent, ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index d917e7d5..1201937c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -12,7 +12,9 @@ import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.ItemTimetableBinding +import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject @@ -29,9 +31,14 @@ class TimetableAdapter @Inject constructor() : TimetableItemType.SMALL -> SmallViewHolder( ItemTimetableSmallBinding.inflate(inflater, parent, false) ) + TimetableItemType.NORMAL -> NormalViewHolder( ItemTimetableBinding.inflate(inflater, parent, false) ) + + TimetableItemType.EMPTY -> EmptyViewHolder( + ItemTimetableEmptyBinding.inflate(inflater, parent, false) + ) } } @@ -40,12 +47,12 @@ class TimetableAdapter @Inject constructor() : position: Int, payloads: MutableList ) { - if (payloads.isEmpty()) return super.onBindViewHolder(holder, position, payloads) - - if (holder is NormalViewHolder) updateTimeLeft( - binding = holder.binding, - timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft, - ) + if (payloads.isNotEmpty() && holder is NormalViewHolder) { + updateTimeLeft( + binding = holder.binding, + timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft, + ) + } else super.onBindViewHolder(holder, position, payloads) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { @@ -54,10 +61,16 @@ class TimetableAdapter @Inject constructor() : binding = holder.binding, item = getItem(position) as TimetableItem.Small, ) + is NormalViewHolder -> bindNormalView( binding = holder.binding, item = getItem(position) as TimetableItem.Normal, ) + + is EmptyViewHolder -> bindEmptyView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Empty, + ) } } @@ -100,6 +113,19 @@ class TimetableAdapter @Inject constructor() : } } + private fun bindEmptyView(binding: ItemTimetableEmptyBinding, item: TimetableItem.Empty) { + with(binding) { + timetableEmptyItemNumber.text = when (item.numFrom) { + item.numTo -> item.numFrom.toString() + else -> "${item.numFrom}-${item.numTo}" + } + timetableEmptyItemSubject.text = timetableEmptyItemSubject.context.getPlural( + R.plurals.timetable_no_lesson, + item.numTo - item.numFrom + 1 + ) + } + } + private fun updateTimeLeft(binding: ItemTimetableBinding, timeLeft: TimeLeft?) { with(binding) { when { @@ -137,6 +163,7 @@ class TimetableAdapter @Inject constructor() : timetableItemTimeLeft.visibility = VISIBLE timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished) } + else -> { timetableItemTimeUntil.visibility = GONE timetableItemTimeLeft.visibility = GONE @@ -191,7 +218,8 @@ class TimetableAdapter @Inject constructor() : ) } else { timetableItemDescription.visibility = GONE - timetableItemRoom.isVisible = lesson.room.isNotBlank() || lesson.roomOld.isNotBlank() + timetableItemRoom.isVisible = + lesson.room.isNotBlank() || lesson.roomOld.isNotBlank() timetableItemGroup.isVisible = item.showGroupsInPlan && lesson.group.isNotBlank() timetableItemTeacher.visibility = VISIBLE } @@ -274,6 +302,9 @@ class TimetableAdapter @Inject constructor() : private class SmallViewHolder(val binding: ItemTimetableSmallBinding) : RecyclerView.ViewHolder(binding.root) + private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) : + RecyclerView.ViewHolder(binding.root) + companion object { private val differ = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean = @@ -281,9 +312,11 @@ class TimetableAdapter @Inject constructor() : oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> { oldItem.lesson.start == newItem.lesson.start } + oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> { oldItem.lesson.start == newItem.lesson.start } + else -> oldItem == newItem } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt index 92716ace..105ece38 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableItem.kt @@ -16,6 +16,11 @@ sealed class TimetableItem(val type: TimetableItemType) { val timeLeft: TimeLeft?, val onClick: (Timetable) -> Unit, ) : TimetableItem(TimetableItemType.NORMAL) + + data class Empty( + val numFrom: Int, + val numTo: Int + ) : TimetableItem(TimetableItemType.EMPTY) } data class TimeLeft( @@ -27,4 +32,5 @@ data class TimeLeft( enum class TimetableItemType { SMALL, NORMAL, + EMPTY } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index d0687408..0f8395de 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,23 +1,44 @@ package io.github.wulkanowy.ui.modules.timetable -import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS +import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS import io.github.wulkanowy.data.enums.TimetableMode +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.isJustFinished +import io.github.wulkanowy.utils.isShowTimeUntil +import io.github.wulkanowy.utils.left +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.until import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.Instant import java.time.LocalDate -import java.time.LocalDate.* -import java.util.* +import java.time.LocalDate.now +import java.time.LocalDate.of +import java.time.LocalDate.ofEpochDay +import java.util.Timer import javax.inject.Inject import kotlin.concurrent.timer @@ -192,16 +213,38 @@ class TimetablePresenter @Inject constructor( compareBy({ item -> item.number }, { item -> !item.isStudentPlan }) ) - return filteredItems.mapIndexed { i, it -> - if (it.isStudentPlan) TimetableItem.Normal( - lesson = it, - showGroupsInPlan = prefRepository.showGroupsInPlan, - timeLeft = filteredItems.getTimeLeftForLesson(it, i), - onClick = ::onTimetableItemSelected - ) else TimetableItem.Small( - lesson = it, - onClick = ::onTimetableItemSelected - ) + var prevNum = when (prefRepository.showTimetableGaps) { + BETWEEN_AND_BEFORE_LESSONS -> 0 + else -> null + } + return buildList { + filteredItems.forEachIndexed { i, it -> + if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) { + val emptyLesson = TimetableItem.Empty( + numFrom = prevNum!! + 1, + numTo = it.number - 1 + ) + add(emptyLesson) + } + + if (it.isStudentPlan) { + val normalLesson = TimetableItem.Normal( + lesson = it, + showGroupsInPlan = prefRepository.showGroupsInPlan, + timeLeft = filteredItems.getTimeLeftForLesson(it, i), + onClick = ::onTimetableItemSelected + ) + add(normalLesson) + } else { + val smallLesson = TimetableItem.Small( + lesson = it, + onClick = ::onTimetableItemSelected + ) + add(smallLesson) + } + + prevNum = it.number + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index d545413d..4e0578e2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -16,6 +16,9 @@ import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS +import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository @@ -24,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey import io.github.wulkanowy.utils.getCompatColor +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber @@ -35,11 +39,12 @@ class TimetableWidgetFactory( private val studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val sharedPref: SharedPrefProvider, + private val prefRepository: PreferencesRepository, private val context: Context, private val intent: Intent? ) : RemoteViewsService.RemoteViewsFactory { - private var lessons = emptyList() + private var items = emptyList() private var timetableCanceledColor: Int? = null @@ -47,18 +52,13 @@ class TimetableWidgetFactory( private var timetableChangeColor: Int? = null - private var lastSyncInstant: Instant? = null - override fun getLoadingView() = null override fun hasStableIds() = true - override fun getCount() = when { - lessons.isEmpty() -> 0 - else -> lessons.size + 1 - } + override fun getCount() = items.size - override fun getViewTypeCount() = 2 + override fun getViewTypeCount() = 3 override fun getItemId(position: Int) = position.toLong() @@ -75,9 +75,10 @@ class TimetableWidgetFactory( runBlocking { val student = getStudent(studentId) ?: return@runBlocking val semester = semesterRepository.getCurrentSemester(student) - lessons = getLessons(student, semester, date) - lastSyncInstant = - timetableRepository.getLastRefreshTimestamp(semester, date, date) + items = createItems( + lessons = getLessons(student, semester, date), + lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) + ) if (date == LocalDate.now()) { updateTodayLastLessonEnd(appWidgetId) } @@ -101,8 +102,33 @@ class TimetableWidgetFactory( return lessons.sortedBy { it.number } } + private fun createItems( + lessons: List, + lastSync: Instant?, + ): List { + var prevNum = when (prefRepository.showTimetableGaps) { + BETWEEN_AND_BEFORE_LESSONS -> 0 + else -> null + } + return buildList { + lessons.forEach { + if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) { + val emptyItem = TimetableWidgetItem.Empty( + numFrom = prevNum!! + 1, + numTo = it.number - 1 + ) + add(emptyItem) + } + add(TimetableWidgetItem.Normal(it)) + prevNum = it.number + } + add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN)) + } + } + private fun updateTodayLastLessonEnd(appWidgetId: Int) { - val todayLastLessonEnd = lessons.maxOfOrNull { it.end } ?: return + val todayLastLessonEnd = items.filterIsInstance() + .maxOfOrNull { it.lesson.end } ?: return val key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId) sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) } @@ -112,15 +138,15 @@ class TimetableWidgetFactory( } override fun getViewAt(position: Int): RemoteViews? { - if (position == lessons.size) { - val synchronizationInstant = lastSyncInstant ?: Instant.MIN - val synchronizationText = getSynchronizationInfoText(synchronizationInstant) - return RemoteViews(context.packageName, R.layout.item_widget_timetable_footer).apply { - setTextViewText(R.id.timetableWidgetSynchronizationTime, synchronizationText) - } + return when (val item = items.getOrNull(position) ?: return null) { + is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item) + is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item) + is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item) } + } - val lesson = lessons.getOrNull(position) ?: return null + private fun getNormalItemRemoteView(item: TimetableWidgetItem.Normal): RemoteViews { + val lesson = item.lesson val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE) val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE) @@ -130,30 +156,63 @@ class TimetableWidgetFactory( setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime) setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime) setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) + setTextViewText(R.id.timetableWidgetItemTeacher, lesson.teacher) setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent()) } - updateTheme() clearLessonStyles(remoteViews) - if (lesson.room.isBlank()) { remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) } else { remoteViews.setTextViewText(R.id.timetableWidgetItemRoom, lesson.room) } - when { lesson.canceled -> applyCancelledLessonStyles(remoteViews) lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles( - remoteViews, lesson + remoteViews = remoteViews, + lesson = lesson, ) } - return remoteViews } + private fun getEmptyItemRemoteView(item: TimetableWidgetItem.Empty): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_empty + ).apply { + setTextViewText( + R.id.timetableWidgetEmptyItemNumber, + when (item.numFrom) { + item.numTo -> item.numFrom.toString() + else -> "${item.numFrom}-${item.numTo}" + } + ) + setTextViewText( + R.id.timetableWidgetEmptyItemText, + context.getPlural( + R.plurals.timetable_no_lesson, + item.numTo - item.numFrom + 1 + ) + ) + setOnClickFillInIntent(R.id.timetableWidgetEmptyItemContainer, Intent()) + } + } + + private fun getSynchronizedItemRemoteView(item: TimetableWidgetItem.Synchronized): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_footer + ).apply { + setTextViewText( + R.id.timetableWidgetSynchronizationTime, + getSynchronizationInfoText(item.timestamp) + ) + } + } + private fun updateTheme() { when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt new file mode 100644 index 00000000..166b1a8f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetItem.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.ui.modules.timetablewidget + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.Instant + +sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) { + + data class Normal( + val lesson: Timetable, + ) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL) + + data class Empty( + val numFrom: Int, + val numTo: Int + ) : TimetableWidgetItem(TimetableWidgetItemType.EMPTY) + + data class Synchronized( + val timestamp: Instant, + ) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED) +} + +enum class TimetableWidgetItemType { + NORMAL, + EMPTY, + SYNCHRONIZED, +} diff --git a/app/src/main/res/layout/item_timetable_empty.xml b/app/src/main/res/layout/item_timetable_empty.xml new file mode 100644 index 00000000..12fddb75 --- /dev/null +++ b/app/src/main/res/layout/item_timetable_empty.xml @@ -0,0 +1,43 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_widget_timetable_empty.xml b/app/src/main/res/layout/item_widget_timetable_empty.xml new file mode 100644 index 00000000..a48b3645 --- /dev/null +++ b/app/src/main/res/layout/item_widget_timetable_empty.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 45600574..8872b7ab 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -51,6 +51,11 @@ Średnia ze średnich z obu semestrów Średnia wszystkich ocen z całego roku + + Nie pokauj + Tylko między lekcjami + Przed i między lekcjami + Szczęśliwy numerek Nieprzeczytane wiadomości diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 0c1bbf78..a2b5510e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -185,6 +185,12 @@ Zmiana sali z %1$s na %2$s Zmiana nauczyciela z %1$s na %2$s Zmiana przedmiotu z %1$s na %2$s + + Brak lekcji + Brak lekcji + Brak lekcji + Brak lekcji + Zmiana planu lekcji Zmiany planu lekcji @@ -700,6 +706,7 @@ Rozwijanie ocen Oznaczaj bieżącą lekcję Pokazuj grupę obok przedmiotu + Pokazuj puste kafelki gdzie nie ma lekcji Pokazuj listę wykresów w ocenach klasy Pokazuj przedmioty bez ocen Schemat kolorów ocen diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index fefd9b13..8d69f25c 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -23,6 +23,7 @@ no alphabetic false + between false false 0 diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index e7fa542a..c48381e8 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -28,6 +28,7 @@ show_whole_class_plan show_groups_in_plan timetable_show_timers + timetable_show_gaps subjects_without_grades optional_arithmetic_average message_draft diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index 312f0b87..f56707c8 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -123,6 +123,17 @@ all_year + + Don\'t show + Only between lessons + Before and between lessons + + + no_gaps + between + before_and_between + + Lucky number Unread messages diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9dc7e796..ce277bdc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -186,6 +186,10 @@ Change of room from %1$s to %2$s Change of teacher from %1$s to %2$s Change of subject from %1$s to %2$s + + No lesson + No lessons + Timetable change Timetable changes @@ -690,6 +694,7 @@ Grades expanding Mark current lesson Show groups next to subjects + Show empty tiles where there\'s no lesson Show chart list in class grades Show subjects without grades Grades color scheme diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index 62216c76..7177d396 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -111,5 +111,13 @@ app:title="@string/pref_view_timetable_show_whole_class" app:useSimpleSummaryProvider="true" /> --> + From 2e2b13384a214fed9599a713fe7cbeedf0eb9cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 23 Aug 2023 12:24:17 +0200 Subject: [PATCH 71/74] Try to switch to next school year before it starts (#2278) --- app/build.gradle | 2 +- .../data/repositories/SemesterRepository.kt | 4 +-- .../wulkanowy/utils/SemesterExtension.kt | 17 ++++++++-- .../repositories/SemesterRepositoryTest.kt | 23 +++++++------ .../utils/SemesterExtensionKtTest.kt | 34 +++++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 37b165c1..136c5430 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.8' + implementation 'io.github.wulkanowy:sdk:2.0.9-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 92bb3708..dd44df70 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 @@ -41,7 +41,7 @@ class SemesterRepository @Inject constructor( val isRefreshOnModeChangeRequired = when { Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE -> { - semesters.firstOrNull { it.isCurrent }?.let { + semesters.firstOrNull { it.isCurrent() }?.let { 0 == it.diaryId && 0 == it.kindergartenDiaryId } == true } @@ -49,7 +49,7 @@ class SemesterRepository @Inject constructor( } val isRefreshOnNoCurrentAppropriate = - refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } + refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent() } return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate } diff --git a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt index 380d6bf6..e3b8a3b4 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt @@ -1,16 +1,27 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.data.db.entities.Semester +import java.time.LocalDate import java.time.LocalDate.now +import java.time.Month -inline val Semester.isCurrent: Boolean - get() = now() in start..end +fun Semester.isCurrent(now: LocalDate = now()): Boolean { + val shiftedStart = if (start.month == Month.SEPTEMBER) { + start.minusDays(3) + } else start + + val shiftedEnd = if (end.month == Month.AUGUST || end.month == Month.SEPTEMBER) { + end.minusDays(3) + } else end + + return now in shiftedStart..shiftedEnd +} fun List.getCurrentOrLast(): Semester { if (isEmpty()) throw RuntimeException("Empty semester list") // when there is only one current semester - singleOrNull { it.isCurrent }?.let { return it } + singleOrNull { it.isCurrent() }?.let { return it } // when there is more than one current semester - find one with higher id singleOrNull { semester -> semester.semesterId == maxByOrNull { it.semesterId }?.semesterId }?.let { return it } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt index d8256869..31098d2e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt @@ -15,6 +15,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK import io.mockk.just import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Before @@ -81,15 +82,15 @@ class SemesterRepositoryTest { } @Test - fun getSemesters_invalidDiary_scrapper() { + fun getSemesters_invalidDiary_scrapper() = runTest { val badSemesters = listOf( - getSemesterPojo(0, 1, now().minusMonths(6), now().minusMonths(3)), - getSemesterPojo(0, 2, now().minusMonths(3), now()) + getSemesterPojo(0, 2, now().minusMonths(6), now()), + getSemesterPojo(0, 2, now(), now().plusMonths(6)), ) val goodSemesters = listOf( - getSemesterPojo(1, 1, now().minusMonths(6), now().minusMonths(3)), - getSemesterPojo(1, 2, now().minusMonths(3), now()) + getSemesterPojo(1, 2, now().minusMonths(6), now()), + getSemesterPojo(2, 3, now(), now().plusMonths(6)), ) coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( @@ -101,7 +102,9 @@ class SemesterRepositoryTest { coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.insertSemesters(any()) } returns listOf() - val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.SCRAPPER.name)) } + val items = semesterRepository.getSemesters( + student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) + ) assertEquals(2, items.size) assertNotEquals(0, items[0].diaryId) } @@ -188,15 +191,15 @@ class SemesterRepositoryTest { } @Test - fun getSemesters_doubleCurrent_refreshOnNoCurrent() { + fun getSemesters_doubleCurrent_refreshOnNoCurrent() = runTest { val semesters = listOf( - getSemesterEntity(1, 1, now(), now()), - getSemesterEntity(1, 2, now(), now()) + getSemesterEntity(1, 1, now().minusMonths(1), now().plusMonths(1)), + getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1)) ) coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters - val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } + val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) assertEquals(2, items.size) } diff --git a/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt b/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt index b7d3ecc9..e8ba8a87 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/SemesterExtensionKtTest.kt @@ -8,6 +8,40 @@ import kotlin.test.assertEquals class SemesterExtensionKtTest { + @Test + fun `check is first semester is current`() { + val first = getSemesterEntity( + semesterName = 1, + start = LocalDate.of(2023, 9, 1), + end = LocalDate.of(2024, 1, 31), + ) + + // first boundary - school-year start + assertEquals(false, first.isCurrent(LocalDate.of(2023, 8, 28))) + assertEquals(true, first.isCurrent(LocalDate.of(2023, 8, 29))) + + // second boundary + assertEquals(true, first.isCurrent(LocalDate.of(2024, 1, 31))) + assertEquals(false, first.isCurrent(LocalDate.of(2024, 2, 1))) + } + + @Test + fun `check is second semester is current`() { + val second = getSemesterEntity( + semesterName = 2, + start = LocalDate.of(2024, 2, 1), + end = LocalDate.of(2024, 9, 1), + ) + + // first boundary + assertEquals(false, second.isCurrent(LocalDate.of(2024, 1, 31))) + assertEquals(true, second.isCurrent(LocalDate.of(2024, 2, 1))) + + // second boundary - school-year end + assertEquals(true, second.isCurrent(LocalDate.of(2024, 8, 29))) + assertEquals(false, second.isCurrent(LocalDate.of(2024, 8, 30))) + } + @Test(expected = IllegalArgumentException::class) fun `get current semester when current is doubled`() { val semesters = listOf( From fbce9e58d034fe5616736d06bd4e5235f573d593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 23 Aug 2023 19:46:53 +0200 Subject: [PATCH 72/74] New Crowdin updates (#2277) --- app/src/main/res/values-cs/preferences_values.xml | 5 +++++ app/src/main/res/values-cs/strings.xml | 7 +++++++ app/src/main/res/values-da-rDK/preferences_values.xml | 5 +++++ app/src/main/res/values-da-rDK/strings.xml | 5 +++++ app/src/main/res/values-de/preferences_values.xml | 5 +++++ app/src/main/res/values-de/strings.xml | 5 +++++ app/src/main/res/values-es-rES/preferences_values.xml | 5 +++++ app/src/main/res/values-es-rES/strings.xml | 5 +++++ app/src/main/res/values-it-rIT/preferences_values.xml | 5 +++++ app/src/main/res/values-it-rIT/strings.xml | 5 +++++ app/src/main/res/values-ru/preferences_values.xml | 5 +++++ app/src/main/res/values-ru/strings.xml | 7 +++++++ app/src/main/res/values-sk/preferences_values.xml | 5 +++++ app/src/main/res/values-sk/strings.xml | 7 +++++++ app/src/main/res/values-uk/preferences_values.xml | 5 +++++ app/src/main/res/values-uk/strings.xml | 7 +++++++ 16 files changed, 88 insertions(+) diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index 23073adf..2cf40263 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -51,6 +51,11 @@ Průměr z průměrů z obou semestrů Průměr známek z celého roku + + Don\'t show + Only between lessons + Before and between lessons + Šťastné číslo Nepřečtené zprávy diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ff461d42..a63a0aa1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -185,6 +185,12 @@ Změna učebny z %1$s na %2$s Změna učitele z %1$s na %2$s Změna předmětu z %1$s na %2$s + + No lesson + No lessons + No lessons + No lessons + Změna plánu lekcí Změny plánu lekcí @@ -700,6 +706,7 @@ Rozvíjení známek Označit aktuální lekci Zobrazit skupiny vedle předmětů + Show empty tiles where there\'s no lesson Zobrazit seznam grafů v známkách třídy Zobrazit předměty bez známek Známky barevné schéma diff --git a/app/src/main/res/values-da-rDK/preferences_values.xml b/app/src/main/res/values-da-rDK/preferences_values.xml index ac2b6e9e..5aff12de 100644 --- a/app/src/main/res/values-da-rDK/preferences_values.xml +++ b/app/src/main/res/values-da-rDK/preferences_values.xml @@ -51,6 +51,11 @@ Average of averages from both semesters Average of grades from the whole year + + Don\'t show + Only between lessons + Before and between lessons + Lucky number Unread messages diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 259a4264..2abf1a4a 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -171,6 +171,10 @@ Change of room from %1$s to %2$s Change of teacher from %1$s to %2$s Change of subject from %1$s to %2$s + + No lesson + No lessons + Timetable change Timetable changes @@ -612,6 +616,7 @@ Grades expanding Mark current lesson Show groups next to subjects + Show empty tiles where there\'s no lesson Show chart list in class grades Show subjects without grades Grades color scheme diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index d9cac195..d1001c74 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -51,6 +51,11 @@ Durchschnittswert der Durchschnittswerte beider Semester Durchschnitt der Noten aus dem ganzen Jahr + + Don\'t show + Only between lessons + Before and between lessons + Glückszahl Ungelesene Nachrichten diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1836d047..f08a504a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -171,6 +171,10 @@ Änderung des Raumes von %1$s zu %2$s Wechsel des Lehrers von %1$s zu %2$s Thema von %1$s zu %2$s wechseln + + No lesson + No lessons + Änderung des Zeitplans Änderungen des Zeitplans @@ -612,6 +616,7 @@ Steigende Sorten Aktuelle Lektion markieren Gruppen neben Schulfächen anzeigen + Show empty tiles where there\'s no lesson Liste der Diagramme in Klassenbewertungen anzeigen Schulfächer ohne Noten anzeigen Farbschema der Noten diff --git a/app/src/main/res/values-es-rES/preferences_values.xml b/app/src/main/res/values-es-rES/preferences_values.xml index ac2b6e9e..5aff12de 100644 --- a/app/src/main/res/values-es-rES/preferences_values.xml +++ b/app/src/main/res/values-es-rES/preferences_values.xml @@ -51,6 +51,11 @@ Average of averages from both semesters Average of grades from the whole year + + Don\'t show + Only between lessons + Before and between lessons + Lucky number Unread messages diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 259a4264..2abf1a4a 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -171,6 +171,10 @@ Change of room from %1$s to %2$s Change of teacher from %1$s to %2$s Change of subject from %1$s to %2$s + + No lesson + No lessons + Timetable change Timetable changes @@ -612,6 +616,7 @@ Grades expanding Mark current lesson Show groups next to subjects + Show empty tiles where there\'s no lesson Show chart list in class grades Show subjects without grades Grades color scheme diff --git a/app/src/main/res/values-it-rIT/preferences_values.xml b/app/src/main/res/values-it-rIT/preferences_values.xml index ac2b6e9e..5aff12de 100644 --- a/app/src/main/res/values-it-rIT/preferences_values.xml +++ b/app/src/main/res/values-it-rIT/preferences_values.xml @@ -51,6 +51,11 @@ Average of averages from both semesters Average of grades from the whole year + + Don\'t show + Only between lessons + Before and between lessons + Lucky number Unread messages diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 259a4264..2abf1a4a 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -171,6 +171,10 @@ Change of room from %1$s to %2$s Change of teacher from %1$s to %2$s Change of subject from %1$s to %2$s + + No lesson + No lessons + Timetable change Timetable changes @@ -612,6 +616,7 @@ Grades expanding Mark current lesson Show groups next to subjects + Show empty tiles where there\'s no lesson Show chart list in class grades Show subjects without grades Grades color scheme diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 8a8c260d..df3629c0 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -51,6 +51,11 @@ Средняя из средних оценок семестров Средняя из оценок со всего года + + Don\'t show + Only between lessons + Before and between lessons + Счастливый номер Непрочитанные письма diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 60697174..d075dac6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -185,6 +185,12 @@ Аудитория изменена с %1$s на %2$s Учитель изменён с %1$s на %2$s Тема изменена с %1$s на %2$s + + No lesson + No lessons + No lessons + No lessons + Изменение расписания Изменения расписания @@ -700,6 +706,7 @@ Разворачивание оценок Отметить текущий урок Показать группы рядом с темами + Show empty tiles where there\'s no lesson Показывать диаграммы в оценках класса Показать предметы без оценок Цветовая схема оценок diff --git a/app/src/main/res/values-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml index e4331315..fd393394 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -51,6 +51,11 @@ Priemer z priemerov z oboch semestrov Priemer známok z celého roka + + Don\'t show + Only between lessons + Before and between lessons + Šťastné číslo Neprečítané správy diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 20d8818b..d1990e35 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -185,6 +185,12 @@ Zmena učebne z %1$s na %2$s Zmena učiteľa z %1$s na %2$s Zmena predmetu z %1$s na %2$s + + No lesson + No lessons + No lessons + No lessons + Zmena plánu lekcií Zmeny plánu lekcií @@ -700,6 +706,7 @@ Rozvijanie známok Označiť aktuálne lekciu Zobraziť skupiny vedľa predmetov + Show empty tiles where there\'s no lesson Zobraziť zoznam grafov v známkach triedy Zobraziť predmety bez známok Známky farebnú schému diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index 44acd18e..55cf905b 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -51,6 +51,11 @@ Середнє значення з обох семестрів Середня оцінка з цілого року + + Don\'t show + Only between lessons + Before and between lessons + Щасливий номер Непрочитані листи diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index db5c3cb0..876562d3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -185,6 +185,12 @@ Зміна аудіторії з %1$s на %2$s Зміна вчителя з %1$s на %2$s Зміна теми з %1$s на %2$s + + No lesson + No lessons + No lessons + No lessons + Зміна у розкладі Зміни у розкладі @@ -700,6 +706,7 @@ Розгортання оцінок Позначити поточний урок Показувати групи поруч з темами + Show empty tiles where there\'s no lesson Показувати діаграми в оцінках класу Показати предмети без оцінок Схема кольорів оцінок From 3dfc55c4d196cb767704e8f6f73ac1e25f611df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 24 Aug 2023 11:33:40 +0200 Subject: [PATCH 73/74] Add admin messages to login screen (#2280) --- app/build.gradle | 2 +- .../57.json | 2443 +++++++++++++++++ .../java/io/github/wulkanowy/data/Resource.kt | 2 +- .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../io/github/wulkanowy/data/db/Converters.kt | 6 + .../data/db/entities/AdminMessage.kt | 4 +- .../data/db/migrations/Migration57.kt | 10 + .../wulkanowy/data/enums/MessageType.kt | 9 + .../repositories/AdminMessageRepository.kt | 45 +- .../GetAppropriateAdminMessageUseCase.kt | 64 + .../dashboard/DashboardItemMoveCallback.kt | 3 +- .../modules/dashboard/DashboardPresenter.kt | 34 +- .../dashboard/adapters/DashboardAdapter.kt | 45 +- .../viewholders/AdminMessageViewHolder.kt | 52 + .../modules/login/form/LoginFormFragment.kt | 15 + .../modules/login/form/LoginFormPresenter.kt | 40 +- .../ui/modules/login/form/LoginFormView.kt | 5 + .../main/res/layout/fragment_login_form.xml | 15 +- .../login/form/LoginFormPresenterTest.kt | 10 + 19 files changed, 2722 insertions(+), 85 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt create mode 100644 app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt diff --git a/app/build.gradle b/app/build.gradle index 136c5430..d0ae8894 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,7 +27,7 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 130 + versionCode 131 // todo: already bumped for 2.1.0 version versionName "2.0.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json new file mode 100644 index 00000000..2eff1223 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json @@ -0,0 +1,2443 @@ +{ + "formatVersion": 1, + "database": { + "version": 57, + "identityHash": "d15dbe7d7e4d7df98ec98d9a3a4b5fcd", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `scrapper_domain_suffix` TEXT NOT NULL DEFAULT '', `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": "scrapperDomainSuffix", + "columnName": "scrapper_domain_suffix", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "orders": [], + "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, `kindergarten_diary_id` INTEGER NOT NULL DEFAULT 0, `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": "kindergartenDiaryId", + "columnName": "kindergarten_diary_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "kindergarten_diary_id", + "semester_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_kindergarten_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `kindergarten_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `message_global_key` TEXT NOT NULL, `mailbox_key` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `correspondents` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `read_by` INTEGER, `unread_by` INTEGER, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `content` TEXT NOT NULL, `sender` TEXT, `recipients` TEXT)", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mailboxKey", + "columnName": "mailbox_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "correspondents", + "columnName": "correspondents", + "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": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": false + }, + { + "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": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "recipients", + "columnName": "recipients", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`message_global_key`, `url`, `filename`))", + "fields": [ + { + "fieldPath": "messageGlobalKey", + "columnName": "message_global_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_global_key", + "url", + "filename" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `email` TEXT NOT NULL, `symbol` TEXT NOT NULL, `schoolId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `studentName` TEXT NOT NULL, `schoolNameShort` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`globalKey`))", + "fields": [ + { + "fieldPath": "globalKey", + "columnName": "globalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolId", + "columnName": "schoolId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "studentName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolNameShort", + "columnName": "schoolNameShort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "globalKey" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mailboxGlobalKey` TEXT NOT NULL, `studentMailboxGlobalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `schoolShortName` TEXT NOT NULL, `type` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "mailboxGlobalKey", + "columnName": "mailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentMailboxGlobalKey", + "columnName": "studentMailboxGlobalKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "schoolShortName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_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": "user_login_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `repeat_id` BLOB DEFAULT NULL, `is_added_by_user` INTEGER NOT NULL DEFAULT 0)", + "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 + }, + { + "fieldPath": "repeatId", + "columnName": "repeat_id", + "affinity": "BLOB", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_login_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": "userLoginId", + "columnName": "user_login_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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `destination` TEXT NOT NULL DEFAULT '{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}', `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": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}'" + }, + { + "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": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, `types` TEXT NOT NULL DEFAULT '[]', `is_dismissible` INTEGER 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": "types", + "columnName": "types", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "isDismissible", + "columnName": "is_dismissible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "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, 'd15dbe7d7e4d7df98ec98d9a3a4b5fcd')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt index 6b611e47..2c5bf0ea 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -148,7 +148,7 @@ inline fun networkBoundResource( crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, crossinline onFetchFailed: (Throwable) -> Unit = { }, crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline mapResult: (ResultType) -> T + crossinline mapResult: (ResultType) -> T, ) = flow { emit(Resource.Loading()) 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 882a7016..48a2942c 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 @@ -50,6 +50,7 @@ import javax.inject.Singleton AutoMigration(from = 51, to = 52), AutoMigration(from = 54, to = 55, spec = Migration55::class), AutoMigration(from = 55, to = 56), + AutoMigration(from = 56, to = 57, spec = Migration57::class), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -58,7 +59,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 56 + const val VERSION_SCHEMA = 57 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), 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 9d3beae1..7bc8d12a 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,6 +1,7 @@ package io.github.wulkanowy.data.db import androidx.room.TypeConverter +import io.github.wulkanowy.data.enums.MessageType import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.utils.toTimestamp import kotlinx.serialization.SerializationException @@ -68,4 +69,9 @@ class Converters { @TypeConverter fun stringToDestination(destination: String): Destination = json.decodeFromString(destination) + @TypeConverter + fun messageTypesToString(types: List): String = json.encodeToString(types) + + @TypeConverter + fun stringToMessageTypes(text: String): List = json.decodeFromString(text) } 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 index 97fec69b..875c2a3a 100644 --- 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import io.github.wulkanowy.data.enums.MessageType import kotlinx.serialization.Serializable @Serializable @@ -33,7 +34,8 @@ data class AdminMessage( val priority: String, - val type: String, + @ColumnInfo(name = "types", defaultValue = "[]") + val types: List = emptyList(), @ColumnInfo(name = "is_dismissible") val isDismissible: Boolean = false diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt new file mode 100644 index 00000000..2fc8718f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration57.kt @@ -0,0 +1,10 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.DeleteColumn +import androidx.room.migration.AutoMigrationSpec + +@DeleteColumn( + tableName = "AdminMessages", + columnName = "type", +) +class Migration57 : AutoMigrationSpec diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt new file mode 100644 index 00000000..531684e4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.data.enums + +enum class MessageType { + GENERAL_MESSAGE, + DASHBOARD_MESSAGE, + LOGIN_MESSAGE, + PASS_RESET_MESSAGE, + ERROR_OVERRIDE, +} 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 index c9655b72..b831ee75 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt @@ -1,10 +1,11 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.Resource 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.data.db.entities.AdminMessage import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.utils.AppInfo +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -13,34 +14,20 @@ import javax.inject.Singleton class AdminMessageRepository @Inject constructor( private val adminMessageService: AdminMessageService, private val adminMessageDao: AdminMessageDao, - private val appInfo: AppInfo ) { + private val saveFetchResultMutex = Mutex() - suspend fun getAdminMessages(student: Student) = networkBoundResource( - mutex = saveFetchResultMutex, - isResultEmpty = { it == null }, - query = { adminMessageDao.loadAll() }, - fetch = { adminMessageService.getAdminMessages() }, - shouldFetch = { true }, - saveFetchResult = { oldItems, newItems -> - adminMessageDao.removeOldAndSaveNew(oldItems, newItems) - }, - 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 } - } - ) + fun getAdminMessages(): Flow>> = + networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { false }, + query = { adminMessageDao.loadAll() }, + fetch = { adminMessageService.getAdminMessages() }, + shouldFetch = { true }, + saveFetchResult = { oldItems, newItems -> + adminMessageDao.removeOldAndSaveNew(oldItems, newItems) + }, + showSavedOnLoading = false, + ) } diff --git a/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt new file mode 100644 index 00000000..b55bf899 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/adminmessage/GetAppropriateAdminMessageUseCase.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.domain.adminmessage + +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.repositories.AdminMessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.utils.AppInfo +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetAppropriateAdminMessageUseCase @Inject constructor( + private val adminMessageRepository: AdminMessageRepository, + private val preferencesRepository: PreferencesRepository, + private val appInfo: AppInfo +) { + + operator fun invoke(student: Student, type: MessageType): Flow> { + return invoke(student.scrapperBaseUrl, type) + } + + operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow> { + return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages -> + adminMessages + .asSequence() + .filter { it.isNotDismissed() } + .filter { it.isVersionMatch() } + .filter { it.isRegisterHostMatch(scrapperBaseUrl) } + .filter { it.isFlavorMatch() } + .filter { it.isTypeMatch(type) } + .maxByOrNull { it.id } + } + } + + private fun AdminMessage.isNotDismissed(): Boolean { + return id !in preferencesRepository.dismissedAdminMessageIds + } + + private fun AdminMessage.isRegisterHostMatch(scrapperBaseUrl: String): Boolean { + return targetRegisterHost?.let { + scrapperBaseUrl.contains(it, true) + } ?: true + } + + private fun AdminMessage.isFlavorMatch(): Boolean { + return targetFlavor?.equals(appInfo.buildFlavor, true) ?: true + } + + private fun AdminMessage.isVersionMatch(): Boolean { + val isCorrectMaxVersion = versionMax?.let { it >= appInfo.versionCode } ?: true + val isCorrectMinVersion = versionMin?.let { it <= appInfo.versionCode } ?: true + + return isCorrectMaxVersion && isCorrectMinVersion + } + + private fun AdminMessage.isTypeMatch(messageType: MessageType): Boolean { + if (messageType in types) return true + if (MessageType.GENERAL_MESSAGE in types) return true + + return false + } +} 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 9c15acc3..f033b594 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.dashboard import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import java.util.* class DashboardItemMoveCallback( @@ -55,5 +56,5 @@ class DashboardItemMoveCallback( } private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean - get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder + get() = this is 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 ac2c896d..ecf084c6 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,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.AdminMessage 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.enums.MessageType import io.github.wulkanowy.data.repositories.* +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AdsHelper @@ -32,7 +34,7 @@ class DashboardPresenter @Inject constructor( private val conferenceRepository: ConferenceRepository, private val preferencesRepository: PreferencesRepository, private val schoolAnnouncementRepository: SchoolAnnouncementRepository, - private val adminMessageRepository: AdminMessageRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, private val adsHelper: AdsHelper ) : BasePresenter(errorHandler, studentRepository) { @@ -159,19 +161,23 @@ class DashboardPresenter @Inject constructor( DashboardItem.Type.ACCOUNT -> { updateData(DashboardItem.Account(student), forceRefresh) } + DashboardItem.Type.HORIZONTAL_GROUP -> { loadHorizontalGroup(student, forceRefresh) } + DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh) DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh) DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh) DashboardItem.Type.ANNOUNCEMENTS -> { loadSchoolAnnouncements(student, forceRefresh) } + DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh) DashboardItem.Type.CONFERENCES -> { loadConferences(student, forceRefresh) } + DashboardItem.Type.ADS -> loadAds(forceRefresh) DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh) } @@ -355,6 +361,7 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.GRADES } } + is Resource.Success -> { Timber.i("Loading dashboard grades result: Success") updateData( @@ -365,6 +372,7 @@ class DashboardPresenter @Inject constructor( forceRefresh ) } + is Resource.Error -> { Timber.i("Loading dashboard grades result: An exception occurred") errorHandler.dispatch(it.error) @@ -402,12 +410,14 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.LESSONS } } + is Resource.Success -> { Timber.i("Loading dashboard lessons result: Success") updateData( DashboardItem.Lessons(it.data), forceRefresh ) } + is Resource.Error -> { Timber.i("Loading dashboard lessons result: An exception occurred") errorHandler.dispatch(it.error) @@ -457,10 +467,12 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.HOMEWORK } } + is Resource.Success -> { Timber.i("Loading dashboard homework result: Success") updateData(DashboardItem.Homework(it.data), forceRefresh) } + is Resource.Error -> { Timber.i("Loading dashboard homework result: An exception occurred") errorHandler.dispatch(it.error) @@ -489,10 +501,12 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS } } + is Resource.Success -> { Timber.i("Loading dashboard announcements result: Success") updateData(DashboardItem.Announcements(it.data), forceRefresh) } + is Resource.Error -> { Timber.i("Loading dashboard announcements result: An exception occurred") errorHandler.dispatch(it.error) @@ -530,10 +544,12 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.EXAMS } } + is Resource.Success -> { Timber.i("Loading dashboard exams result: Success") updateData(DashboardItem.Exams(it.data), forceRefresh) } + is Resource.Error -> { Timber.i("Loading dashboard exams result: An exception occurred") errorHandler.dispatch(it.error) @@ -569,10 +585,12 @@ class DashboardPresenter @Inject constructor( firstLoadedItemList += DashboardItem.Type.CONFERENCES } } + is Resource.Success -> { Timber.i("Loading dashboard conferences result: Success") updateData(DashboardItem.Conferences(it.data), forceRefresh) } + is Resource.Error -> { Timber.i("Loading dashboard conferences result: An exception occurred") errorHandler.dispatch(it.error) @@ -584,12 +602,12 @@ class DashboardPresenter @Inject constructor( } private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { - flatResourceFlow { adminMessageRepository.getAdminMessages(student) } - .filter { - val data = it.dataOrNull ?: return@filter true - val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds - !isDismissed - } + flatResourceFlow { + getAppropriateAdminMessageUseCase( + student = student, + type = MessageType.DASHBOARD_MESSAGE, + ) + } .onEach { when (it) { is Resource.Loading -> { @@ -597,6 +615,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) return@onEach updateData(DashboardItem.AdminMessages(), forceRefresh) } + is Resource.Success -> { Timber.i("Loading dashboard admin message result: Success") updateData( @@ -604,6 +623,7 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh ) } + is Resource.Error -> { Timber.i("Loading dashboard admin message result: An exception occurred") Timber.e(it.error) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt index 4ad4e9d6..7c74cae8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardAdapter.kt @@ -1,8 +1,6 @@ package io.github.wulkanowy.ui.modules.dashboard.adapters 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 @@ -24,6 +22,7 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.* import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.utils.* import timber.log.Timber import java.time.Duration @@ -109,7 +108,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter AdminMessageViewHolder( - ItemDashboardAdminMessageBinding.inflate(inflater, parent, false) + ItemDashboardAdminMessageBinding.inflate(inflater, parent, false), + onAdminMessageDismissClickListener = onAdminMessageDismissClickListener, + onAdminMessageClickListener = onAdminMessageClickListener, ) DashboardItem.Type.ADS.ordinal -> AdsViewHolder( ItemDashboardAdsBinding.inflate(inflater, parent, false) @@ -128,7 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter bindAnnouncementsViewHolder(holder, position) is ExamsViewHolder -> bindExamsViewHolder(holder, position) is ConferencesViewHolder -> bindConferencesViewHolder(holder, position) - is AdminMessageViewHolder -> bindAdminMessage(holder, position) + is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage) is AdsViewHolder -> bindAdsViewHolder(holder, position) } } @@ -733,39 +734,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter { - context.getThemeAttrColor(R.attr.colorMessageHigh) to - context.getThemeAttrColor(R.attr.colorOnMessageHigh) - } - "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) - dashboardAdminMessageItemDismiss.isVisible = item.isDismissible - dashboardAdminMessageItemDismiss.setTextColor(textColor) - dashboardAdminMessageItemDismiss.setOnClickListener { - onAdminMessageDismissClickListener(item) - } - - root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) - item.destinationUrl?.let { url -> - root.setOnClickListener { onAdminMessageClickListener(url) } - } - } - } - private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) { val item = (items[position] as DashboardItem.Ads).adBanner ?: return val binding = adsViewHolder.binding @@ -819,9 +787,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter Unit, + private val onAdminMessageClickListener: (String?) -> Unit, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: AdminMessage?) { + item ?: return + + val context = binding.root.context + val (backgroundColor, textColor) = when (item.priority) { + "HIGH" -> { + context.getThemeAttrColor(R.attr.colorMessageHigh) to + context.getThemeAttrColor(R.attr.colorOnMessageHigh) + } + "MEDIUM" -> { + context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK + } + else -> null to context.getThemeAttrColor(R.attr.colorOnSurface) + } + + with(binding) { + dashboardAdminMessageItemTitle.text = item.title + dashboardAdminMessageItemTitle.setTextColor(textColor) + dashboardAdminMessageItemDescription.text = item.content + dashboardAdminMessageItemDescription.setTextColor(textColor) + dashboardAdminMessageItemIcon.setColorFilter(textColor) + dashboardAdminMessageItemDismiss.isVisible = item.isDismissible + dashboardAdminMessageItemDismiss.setTextColor(textColor) + dashboardAdminMessageItemDismiss.setOnClickListener { + onAdminMessageDismissClickListener(item) + } + + root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) + item.destinationUrl?.let { url -> + root.setOnClickListener { onAdminMessageClickListener(url) } + } + } + } +} 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 1085ff50..ff7fd864 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 @@ -9,10 +9,12 @@ import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.* @@ -207,6 +209,19 @@ class LoginFormFragment : BaseFragment(R.layout.fragme binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } + override fun showAdminMessage(message: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginFormMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(message) + binding.loginFormMessage.root.isVisible = message != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } + override fun showDomainSuffixInput(show: Boolean) { binding.loginFormDomainSuffixLayout.isVisible = show } 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 85f42841..4e0404d9 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 @@ -1,13 +1,19 @@ package io.github.wulkanowy.ui.modules.login.form import androidx.core.net.toUri +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.flatResourceFlow import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.onResourceLoading import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.onResourceSuccess +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -22,7 +28,9 @@ class LoginFormPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, private val appInfo: AppInfo, - private val analytics: AnalyticsHelper + private val analytics: AnalyticsHelper, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, + private val preferencesRepository: PreferencesRepository, ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null @@ -41,6 +49,31 @@ class LoginFormPresenter @Inject constructor( Timber.i("Entered wrong username or password") } } + + reloadAdminMessage() + } + + private fun reloadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = view?.formHostValue.orEmpty(), + type = MessageType.LOGIN_MESSAGE, + ) + } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch() + } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) } fun onPrivacyLinkClick() { @@ -63,6 +96,7 @@ class LoginFormPresenter @Inject constructor( } updateCustomDomainSuffixVisibility() updateUsernameLabel() + reloadAdminMessage() } } @@ -103,7 +137,9 @@ class LoginFormPresenter @Inject constructor( val email = view?.formUsernameValue.orEmpty().trim() val password = view?.formPassValue.orEmpty().trim() val host = view?.formHostValue.orEmpty().trim() - val domainSuffix = view?.formDomainSuffix.orEmpty().trim() + val domainSuffix = view?.formDomainSuffix.orEmpty().trim().takeIf { + "customSuffix" in host + }.orEmpty() val symbol = view?.formHostSymbol.orEmpty().trim() if (!validateCredentials(email, password, host)) return 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 5fb26062..5b4dcadf 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.form +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData @@ -58,6 +59,10 @@ interface LoginFormView : BaseView { fun showContent(show: Boolean) + fun showAdminMessage(message: AdminMessage?) + + fun openInternetBrowser(url: String) + fun showDomainSuffixInput(show: Boolean) fun showOtherOptionsButton(show: Boolean) diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index 39f3146c..fc5e5f35 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -105,6 +105,18 @@ android:background="?android:attr/listDivider" /> + + Date: Fri, 25 Aug 2023 00:01:36 +0200 Subject: [PATCH 74/74] Version 2.1.0 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d0ae8894..b09078a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 131 // todo: already bumped for 2.1.0 version - versionName "2.0.8" + versionCode 131 + versionName "2.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,7 +161,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.25d - updatePriority = 1 + updatePriority = 3 enabled.set(false) } @@ -191,7 +191,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.0.9-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.1.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' 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 e881cfda..aa934ce9 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,7 +1,8 @@ -Wersja 2.0.8 +Wersja 2.1.0 -— poprawiliśmy wyświetlanie kilku rodzajów zmian w planie lekcji -— dodaliśmy limit znaków w okienku usprawiedliwiania -— naprawiliśmy wyświetlanie frekwencji w szkołach, gdzie działa już system eduOne (ciągle jednak brak opcji usprawiedliwiania) +— dodaliśmy tryb incognito w wiadomościach +— dodaliśmy wyświetlanie pustych lekcji (okienek) w planie lekcji +— poprawiliśmy widżet planu lekcji (będzie teraz trochę bardziej kompaktowy) +— zmieniliśmy datę rozpoczęcia roku szkolnego na 3 dni przed 1 września (sorry) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases