From 73a7255d3a003e15de313387bcbf40c73fa17ae5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:49:30 +0000 Subject: [PATCH 001/545] Bump firebase-bom from 30.3.2 to 30.4.0 (#1968) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6584b0d28..17a9fe42f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:30.3.2') + playImplementation platform('com.google.firebase:firebase-bom:30.4.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 46c29c438eb1a52824dff8d9b0d1d385c3366013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 11:17:19 +0000 Subject: [PATCH 002/545] Bump appcompat from 1.5.0 to 1.5.1 (#1975) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 17a9fe42f..41ead9f3e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "androidx.core:core-ktx:1.8.0" implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.5.1" - implementation "androidx.appcompat:appcompat:1.5.0" + implementation "androidx.appcompat:appcompat:1.5.1" implementation "androidx.fragment:fragment-ktx:1.5.2" implementation "androidx.annotation:annotation:1.4.0" From d3f869c6c2400a5b7fa427a45d89df0add4f5420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 11:18:09 +0000 Subject: [PATCH 003/545] Bump firebase-bom from 30.4.0 to 30.4.1 (#1971) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 41ead9f3e..c2dba71fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:30.4.0') + playImplementation platform('com.google.firebase:firebase-bom:30.4.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From d5d45ed1baa7ec0c52abb0fba9e8bec67c6123e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 11:18:29 +0000 Subject: [PATCH 004/545] Bump play-services-ads from 21.1.0 to 21.2.0 (#1972) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c2dba71fb..7bd4ed15f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -247,7 +247,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' 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:21.1.0' + playImplementation 'com.google.android.gms:play-services-ads:21.2.0' hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300' From a5c636853a9a15563caedf0f5bc90a3a9021d6fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 11:19:05 +0000 Subject: [PATCH 005/545] Bump coil from 2.2.0 to 2.2.1 (#1973) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7bd4ed15f..7b7251f89 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -236,7 +236,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.2.0" + implementation "io.coil-kt:coil:2.2.1" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' From afbfb9761fe61339f364f88ef8fde9d4703cb4e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:38:55 +0000 Subject: [PATCH 006/545] Bump mockk from 1.12.7 to 1.12.8 (#1986) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7b7251f89..67b4fd07d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.4.3" chucker = "3.5.2" - mockk = "1.12.7" + mockk = "1.12.8" coroutines = "1.6.4" } From 3625c5c5187786f781ea446433f09f13b809861d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:39:08 +0000 Subject: [PATCH 007/545] Bump firebase-bom from 30.4.1 to 30.5.0 (#1991) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 67b4fd07d..6f3f1e989 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:30.4.1') + playImplementation platform('com.google.firebase:firebase-bom:30.5.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 4f0519552e3dbce9e5cdecd10685ed1e85aca540 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:39:29 +0000 Subject: [PATCH 008/545] Bump firebase-crashlytics-gradle from 2.9.1 to 2.9.2 (#1988) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 98c9dfb84..d98f6f27e 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.13' classpath 'com.huawei.agconnect:agcp:1.7.1.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513" From f1db993feedb35e746f309dfa0a11c4d0092a0c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:40:36 +0000 Subject: [PATCH 009/545] Bump agconnect-crash from 1.7.1.300 to 1.7.2.300 (#1990) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6f3f1e989..6fddc1a73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:21.2.0' hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.1.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.2.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From a1dc00af42c73255eeec65704df526c3c877aac4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:49:36 +0000 Subject: [PATCH 010/545] Bump agcp from 1.7.1.300 to 1.7.2.300 (#1989) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d98f6f27e..942cf4b66 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.2.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.13' - classpath 'com.huawei.agconnect:agcp:1.7.1.300' + classpath 'com.huawei.agconnect:agcp:1.7.2.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" From 5148ff291b7b5c7b2c33061e7e769325c9728aee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 20:59:41 +0000 Subject: [PATCH 011/545] Bump google-services from 4.3.13 to 4.3.14 (#1992) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 942cf4b66..53fcfda5e 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath 'com.android.tools.build:gradle:7.2.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.13' + classpath 'com.google.gms:google-services:4.3.14' classpath 'com.huawei.agconnect:agcp:1.7.2.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" From 1bbd249275b97b145011fdd30355664984c03bcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:25:58 +0000 Subject: [PATCH 012/545] Bump fragment-ktx from 1.5.2 to 1.5.3 (#1997) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6fddc1a73..e67747d73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.5.1" implementation "androidx.appcompat:appcompat:1.5.1" - implementation "androidx.fragment:fragment-ktx:1.5.2" + implementation "androidx.fragment:fragment-ktx:1.5.3" implementation "androidx.annotation:annotation:1.4.0" implementation "androidx.preference:preference-ktx:1.2.0" From edbe45332ac522a6d3e78ad76a9d3af09edf5960 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:27:43 +0000 Subject: [PATCH 013/545] Bump mockk from 1.12.8 to 1.13.1 (#1996) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e67747d73..eaa81d233 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.4.3" chucker = "3.5.2" - mockk = "1.12.8" + mockk = "1.13.1" coroutines = "1.6.4" } From 8ca41b5ba348483bf9c10a2af927eb387526748e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:28:01 +0000 Subject: [PATCH 014/545] Bump hianalytics from 6.7.0.300 to 6.8.0.300 (#1995) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index eaa81d233..8d33323f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -249,7 +249,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:21.2.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.7.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.2.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From b271c12ebccd7bb6e8ddaf18f68ed09071f385d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:28:31 +0000 Subject: [PATCH 015/545] Bump hilt_version from 2.43.2 to 2.44 (#1994) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 53fcfda5e..e308c579b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.7.10' about_libraries = '10.4.0' - hilt_version = "2.43.2" + hilt_version = "2.44" } repositories { mavenCentral() From 354f51dd707807c7c423b275df7ea4e672689443 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 28 Sep 2022 23:33:05 +0200 Subject: [PATCH 016/545] Fix student average calculation error in grade statistics (#1981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/build.gradle | 2 +- .../grade/summary/GradeSummaryAdapter.kt | 3 ++- .../github/wulkanowy/utils/GradeExtension.kt | 24 ++++++++----------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8d33323f3..1b745f0b6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.7.5" + implementation "io.github.wulkanowy:sdk:2840d9d6d0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt index 082c847e5..8dcade56e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.ItemGradeSummaryBinding import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding +import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid import io.github.wulkanowy.utils.calcFinalAverage import java.util.Locale import javax.inject.Inject @@ -61,7 +62,7 @@ class GradeSummaryAdapter @Inject constructor( if (items.isEmpty()) return val context = binding.root.context - val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) } + val finalItemsCount = items.count { isGradeValid(it.finalGrade) } val calculatedItemsCount = items.count { value -> value.average != 0.0 } val allItemsCount = items.count { !it.subject.equals("zachowanie", true) } val finalAverage = items.calcFinalAverage( diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt index ff65d6376..61924d4e9 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.sdk.scrapper.grades.getGradeValueWithModifier import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid fun List.calcAverage(isOptionalArithmeticAverage: Boolean): Double { @@ -20,20 +21,15 @@ fun List.calcAverage(isOptionalArithmeticAverage: Boolean): Double { } fun List.calcFinalAverage(plusModifier: Double, minusModifier: Double) = asSequence() - .mapNotNull { - if (it.finalGrade.matches("[0-6][+-]?".toRegex())) { - when { - it.finalGrade.endsWith('+') -> { - it.finalGrade.removeSuffix("+").toDouble() + plusModifier - } - it.finalGrade.endsWith('-') -> { - it.finalGrade.removeSuffix("-").toDouble() - minusModifier - } - else -> { - it.finalGrade.toDouble() - } - } - } else null + .mapNotNull { summary -> + val (gradeValue, gradeModifier) = getGradeValueWithModifier(summary.finalGrade) + if (gradeValue == null || gradeModifier == null) return@mapNotNull null + + when { + gradeModifier > 0 -> gradeValue + plusModifier + gradeModifier < 0 -> gradeValue - minusModifier + else -> gradeValue + 0.0 + } } .average() .let { if (it.isNaN()) 0.0 else it } From a523850216a5d9dc82dc3fda9a73e94507c172f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 23:34:06 +0200 Subject: [PATCH 017/545] Bump annotation from 1.4.0 to 1.5.0 (#1998) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1b745f0b6..3127d55c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { implementation "androidx.activity:activity-ktx:1.5.1" implementation "androidx.appcompat:appcompat:1.5.1" implementation "androidx.fragment:fragment-ktx:1.5.3" - implementation "androidx.annotation:annotation:1.4.0" + implementation "androidx.annotation:annotation:1.5.0" implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.recyclerview:recyclerview:1.2.1" From 8114a2376ee3f798e950b4a2deab3ad21c946f19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:43:45 +0000 Subject: [PATCH 018/545] Bump gradle from 7.2.2 to 7.3.0 (#1985) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e308c579b..739d5752e 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.2.2' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' classpath 'com.huawei.agconnect:agcp:1.7.2.300' From 4dc80595ac9d47e8a07fe998e14dffcee83d5885 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:49:22 +0000 Subject: [PATCH 019/545] Bump robolectric from 4.8.2 to 4.9 (#2007) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3127d55c5..ef5b99500 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -263,7 +263,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.8.2' + testImplementation 'org.robolectric:robolectric:4.9' testImplementation "androidx.test:runner:1.4.0" testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "androidx.test:core:1.4.0" From 95a90a7a79f2b2ef0510baaf305257246997f6f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:49:41 +0000 Subject: [PATCH 020/545] Bump mockk from 1.13.1 to 1.13.2 (#2006) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ef5b99500..458f963fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.4.3" chucker = "3.5.2" - mockk = "1.13.1" + mockk = "1.13.2" coroutines = "1.6.4" } From c65303959072a7e4b009973ed593abfd7f6657c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:50:05 +0000 Subject: [PATCH 021/545] Bump coil from 2.2.1 to 2.2.2 (#2004) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 458f963fb..cd4f6cc12 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -236,7 +236,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.2.1" + implementation "io.coil-kt:coil:2.2.2" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' From 37f7f21a033e03ccb976dc8c6c267ef68061cd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 4 Oct 2022 09:51:30 +0200 Subject: [PATCH 022/545] Reorder action buttons on the message preview screen to hide the forward button in overflow menu (#2000) --- .../res/menu/action_menu_message_preview.xml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml index 5011e2356..57cf05ddb 100644 --- a/app/src/main/res/menu/action_menu_message_preview.xml +++ b/app/src/main/res/menu/action_menu_message_preview.xml @@ -8,20 +8,6 @@ android:title="@string/message_reply" app:iconTint="@color/material_on_surface_emphasis_medium" app:showAsAction="ifRoom" /> - - + + From cd037f0ce0f2f2b0107af5114ed3c11f1d03388e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:58:58 +0000 Subject: [PATCH 023/545] Bump kotlin_version from 1.7.10 to 1.7.20 (#2003) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 739d5752e..fb58a44d2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.7.10' + kotlin_version = '1.7.20' about_libraries = '10.4.0' hilt_version = "2.44" } From 3f431022a5b5316babaa89951d13a67d5680299e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 08:08:21 +0000 Subject: [PATCH 024/545] Bump about_libraries from 10.4.0 to 10.5.0 (#2005) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fb58a44d2..9f5b21f4d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.7.20' - about_libraries = '10.4.0' + about_libraries = '10.5.0' hilt_version = "2.44" } repositories { From ad487e680cf6907eba664d7922c12f184974f830 Mon Sep 17 00:00:00 2001 From: Daniel Olczyk <44818681+MRmlik12@users.noreply.github.com> Date: Wed, 5 Oct 2022 22:25:09 +0200 Subject: [PATCH 025/545] Fix grade weight text truncation in grade dialog with large font set (#2009) --- app/src/main/res/layout/dialog_grade.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index 9c52c1d0b..94facb232 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -21,7 +21,7 @@ Date: Tue, 18 Oct 2022 19:39:16 +0000 Subject: [PATCH 026/545] Bump play-services-ads from 21.2.0 to 21.3.0 (#2016) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index cd4f6cc12..a0285de83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -247,7 +247,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' 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:21.2.0' + playImplementation 'com.google.android.gms:play-services-ads:21.3.0' hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.2.300' From 1f11eea9b58e66ce9c7aa6aba7e1dd3ad3ad6078 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:39:36 +0000 Subject: [PATCH 027/545] Bump gradle from 7.3.0 to 7.3.1 (#2015) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9f5b21f4d..5b7b4abbb 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.3.0' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' classpath 'com.huawei.agconnect:agcp:1.7.2.300' From e20c232f8fd2797dd83fae0e6dbdeddde6b000d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:39:54 +0000 Subject: [PATCH 028/545] Bump kotlinx-serialization-json from 1.4.0 to 1.4.1 (#2014) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a0285de83..4ea25d7e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.8.0" From 4c24363599f8dc486a1d7b9a1507977439ef9982 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:40:28 +0000 Subject: [PATCH 029/545] Bump about_libraries from 10.5.0 to 10.5.1 (#2012) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b7b4abbb..5131796bb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.7.20' - about_libraries = '10.5.0' + about_libraries = '10.5.1' hilt_version = "2.44" } repositories { From e91cd188044c94c1022f13f053d74cf243201d5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:41:15 +0000 Subject: [PATCH 030/545] Bump firebase-bom from 30.5.0 to 31.0.0 (#2013) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4ea25d7e2..672a4becc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:30.5.0') + playImplementation platform('com.google.firebase:firebase-bom:31.0.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From a14c4b489bf1b413157500666c828fc4a178c7f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:22:16 +0000 Subject: [PATCH 031/545] Bump material from 1.6.1 to 1.7.0 (#2022) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 672a4becc..8848a413b 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.6.1" + implementation "com.google.android.material:material:1.7.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.2.0' From 4a484dc2ce25472961f0842cb3e05575233b1c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:22:34 +0000 Subject: [PATCH 032/545] Bump fragment-ktx from 1.5.3 to 1.5.4 (#2020) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8848a413b..b15eb651d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.5.1" implementation "androidx.appcompat:appcompat:1.5.1" - implementation "androidx.fragment:fragment-ktx:1.5.3" + implementation "androidx.fragment:fragment-ktx:1.5.4" implementation "androidx.annotation:annotation:1.5.0" implementation "androidx.preference:preference-ktx:1.2.0" From 49b383fbe51078cc375d972aea6b11458906a35f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:22:53 +0000 Subject: [PATCH 033/545] Bump firebase-bom from 31.0.0 to 31.0.1 (#2019) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b15eb651d..6211db2d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:31.0.0') + playImplementation platform('com.google.firebase:firebase-bom:31.0.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 3925a6261b9d921f2ea4b0f051b7092af90d9930 Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Wed, 26 Oct 2022 22:27:37 +0200 Subject: [PATCH 034/545] Add missing CS and SK links in German README (#2018) --- README.de.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.de.md b/README.de.md index b9e1d1ec1..e03557a06 100644 --- a/README.de.md +++ b/README.de.md @@ -2,6 +2,10 @@ [English version of README](README.en.md) +[Česká verze README](README.cs.md) + +[Slovenská verzia README](README.sk.md) + # Wulkanowy [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) From 22a4f509dcc49086ea51939d0b820a479a01aa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 27 Oct 2022 12:41:33 +0200 Subject: [PATCH 035/545] Add installation id to crashlytics and bug report emails (#2024) --- .../github/wulkanowy/utils/AnalyticsHelper.kt | 14 +++------- .../github/wulkanowy/utils/AnalyticsHelper.kt | 26 ++++++++++++++----- .../github/wulkanowy/utils/CrashLogUtils.kt | 9 ++----- .../repositories/PreferencesRepository.kt | 24 ++++++++--------- .../github/wulkanowy/ui/base/ErrorDialog.kt | 10 ++++--- .../ui/modules/about/AboutFragment.kt | 7 ++++- .../modules/login/form/LoginFormFragment.kt | 7 ++++- .../LoginStudentSelectFragment.kt | 10 +++++-- .../login/symbol/LoginSymbolFragment.kt | 7 ++++- app/src/main/res/values/strings.xml | 4 +-- .../github/wulkanowy/utils/AnalyticsHelper.kt | 26 ++++++++++++++----- .../wulkanowy/utils/CrashlyticsUtils.kt | 3 --- 12 files changed, 90 insertions(+), 57 deletions(-) diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index 3bf7e1693..a3eed484a 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -8,15 +8,7 @@ import javax.inject.Singleton @Suppress("UNUSED_PARAMETER") class AnalyticsHelper @Inject constructor() { - fun logEvent(name: String, vararg params: Pair) { - // do nothing - } - - fun setCurrentScreen(activity: Activity, name: String?) { - // do nothing - } - - fun popCurrentScreen(name: String?) { - // do nothing - } + fun logEvent(name: String, vararg params: Pair) = Unit + fun setCurrentScreen(activity: Activity, name: String?) = Unit + fun popCurrentScreen(name: String?) = Unit } diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index 5d33825f1..1f78931ae 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -3,26 +3,38 @@ package io.github.wulkanowy.utils import android.app.Activity import android.content.Context import android.os.Bundle +import com.huawei.agconnect.crash.AGConnectCrash import com.huawei.hms.analytics.HiAnalytics import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.repositories.PreferencesRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class AnalyticsHelper @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + preferencesRepository: PreferencesRepository, + appInfo: AppInfo, ) { private val analytics by lazy { HiAnalytics.getInstance(context) } + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + init { + if (!appInfo.isDebug) { + connectCrash.setUserId(preferencesRepository.installationId) + } + } + fun logEvent(name: String, vararg params: Pair) { Bundle().apply { - params.forEach { - if (it.second == null) return@forEach - when (it.second) { - is String, is String? -> putString(it.first, it.second as String) - is Int, is Int? -> putInt(it.first, it.second as Int) - is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean) + params.forEach { (key, value) -> + if (value == null) return@forEach + when (value) { + is String -> putString(key, value) + is Int -> putInt(key, value) + is Boolean -> putBoolean(key, value) } } analytics.onEvent(name, this) diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt index b0c34f413..377e83666 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.util.Log import com.huawei.agconnect.crash.AGConnectCrash import fr.bipi.tressence.base.FormatterPriorityTree +import fr.bipi.tressence.common.StackTraceRecorder class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { @@ -22,16 +23,10 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter) override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { if (skipLog(priority, tag, message, t)) return - // Disabled due to a bug in the Huawei library - - /*connectCrash.setCustomKey("priority", priority) - connectCrash.setCustomKey("tag", tag.orEmpty()) - connectCrash.setCustomKey("message", message) - if (t != null) { connectCrash.recordException(t) } else { connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) - }*/ + } } } 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 486538e0c..afc262868 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 @@ -10,17 +10,16 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeAverageMode -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Instant +import java.util.* import javax.inject.Inject import javax.inject.Singleton -@OptIn(ExperimentalCoroutinesApi::class) @Singleton class PreferencesRepository @Inject constructor( @ApplicationContext val context: Context, @@ -316,6 +315,16 @@ class PreferencesRepository @Inject constructor( putBoolean(context.getString(R.string.pref_key_ads_enabled), value) } + var installationId: String + get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() + private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } + + init { + if (installationId.isEmpty()) { + installationId = UUID.randomUUID().toString() + } + } + private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) private fun getLong(id: String, default: Int) = @@ -331,23 +340,14 @@ class PreferencesRepository @Inject constructor( private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default)) - private fun getBoolean(id: Int, default: Boolean) = - sharedPref.getBoolean(context.getString(id), default) - private companion object { - + private const val PREF_KEY_INSTALLATION_ID = "installation_id" private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" - private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" - private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" - private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" - private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown" - private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled" - private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index 48c003b7e..e979fa8af 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -4,7 +4,6 @@ import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.os.Bundle -import android.view.LayoutInflater import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog @@ -15,6 +14,7 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.DialogErrorBinding import io.github.wulkanowy.utils.* import javax.inject.Inject @@ -25,6 +25,9 @@ class ErrorDialog : DialogFragment() { @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { private const val ARGUMENT_KEY = "error" @@ -36,7 +39,7 @@ class ErrorDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable - val binding = DialogErrorBinding.inflate(LayoutInflater.from(context)) + val binding = DialogErrorBinding.inflate(layoutInflater) binding.bindErrorDetails(error) return getAlertDialog(binding, error).apply { @@ -99,7 +102,8 @@ class ErrorDialog : DialogFragment() { R.string.about_feedback_template, "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), - "${appInfo.versionName}-${appInfo.buildFlavor}" + "${appInfo.versionName}-${appInfo.buildFlavor}", + preferencesRepository.installationId, ) + "\n" + content, onActivityNotFound = { requireContext().openInternetBrowser( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 701656b55..d7f39e303 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -6,6 +6,7 @@ import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentAboutBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment @@ -30,6 +31,9 @@ class AboutFragment : BaseFragment(R.layout.fragment_about @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + override val versionRes: Triple? get() = context?.run { val buildTimestamp = @@ -185,7 +189,8 @@ class AboutFragment : BaseFragment(R.layout.fragment_about R.string.about_feedback_template, "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), - "${appInfo.versionName}-${appInfo.buildFlavor}" + "${appInfo.versionName}-${appInfo.buildFlavor}", + preferencesRepository.installationId, ), onActivityNotFound = { requireContext().openInternetBrowser( 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 d31f5cf0f..463e192de 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 @@ -10,6 +10,7 @@ import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters +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.login.LoginActivity @@ -32,6 +33,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { fun newInstance() = LoginFormFragment() } @@ -260,8 +264,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme R.string.login_email_text, "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), - appInfo.versionName, + "${appInfo.versionName}-${appInfo.buildFlavor}", "$formHostValue/$formHostSymbol", + preferencesRepository.installationId, lastError ) ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 6c910fe03..c42a4e9d1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity @@ -32,6 +33,9 @@ class LoginStudentSelectFragment : @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { const val ARG_STUDENTS = "STUDENTS" @@ -111,10 +115,12 @@ class LoginStudentSelectFragment : email = "wulkanowyinc@gmail.com", subject = requireContext().getString(R.string.login_email_subject), body = requireContext().getString( - R.string.login_email_text, appInfo.systemModel, + R.string.login_email_text, + "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), - appInfo.versionName, + "${appInfo.versionName}-${appInfo.buildFlavor}", "Select users to log in", + preferencesRepository.installationId, lastError ) ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 58bdf6cef..36c40d156 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -13,6 +13,7 @@ import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity @@ -34,6 +35,9 @@ class LoginSymbolFragment : @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + companion object { private const val SAVED_LOGIN_DATA = "LOGIN_DATA" @@ -159,8 +163,9 @@ class LoginSymbolFragment : R.string.login_email_text, "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), - appInfo.versionName, + "${appInfo.versionName}-${appInfo.buildFlavor}", "$host/${binding.loginSymbolName.text}", + preferencesRepository.installationId, lastError ) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4542547d..48376d216 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,7 +71,7 @@ Discord Send email Zgłoszenie: Problemy z logowaniem - Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nNazwa szkoły i miejscowość: + Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość: Make sure you select the correct UONET+ register variation! I forgot my password Recover your account @@ -512,7 +512,7 @@ Visit the website and help develop the application Licenses Licenses of libraries used in the application - Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\n\nTreść zgłoszenia: + Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nIdentyfikator instalacji: %4$s\nTreść zgłoszenia: diff --git a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index b65325790..3215fa20c 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -4,25 +4,37 @@ import android.app.Activity import android.content.Context import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.repositories.PreferencesRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class AnalyticsHelper @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + preferencesRepository: PreferencesRepository, + appInfo: AppInfo, ) { private val analytics by lazy { FirebaseAnalytics.getInstance(context) } + private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } + + init { + if (!appInfo.isDebug) { + crashlytics.setUserId(preferencesRepository.installationId) + } + } + fun logEvent(name: String, vararg params: Pair) { Bundle().apply { - params.forEach { - if (it.second == null) return@forEach - when (it.second) { - is String, is String? -> putString(it.first, it.second.toString()) - is Int, is Int? -> putInt(it.first, it.second as Int) - is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean) + params.forEach { (key, value) -> + if (value == null) return@forEach + when (value) { + is String -> putString(key, value) + is Int -> putInt(key, value) + is Boolean -> putBoolean(key, value) } } analytics.logEvent(name, this) diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index 410fddf16..f980bc4bb 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -23,9 +23,6 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter) override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { if (skipLog(priority, tag, message, t)) return - crashlytics.setCustomKey("priority", priority) - crashlytics.setCustomKey("tag", tag.orEmpty()) - crashlytics.setCustomKey("message", message) if (t != null) { crashlytics.recordException(t) } else { From 7bee10d5ce0aa85becaa5a5b2a8ecd752ae0b1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 28 Oct 2022 11:08:40 +0200 Subject: [PATCH 036/545] Hide room view in timetable item if there is no room in API (#2026) --- .../github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d6917672a..2f0d697fc 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 @@ -191,7 +191,7 @@ class TimetableAdapter @Inject constructor() : ) } else { timetableItemDescription.visibility = GONE - timetableItemRoom.visibility = VISIBLE + timetableItemRoom.isVisible = lesson.room.isNotBlank() || lesson.roomOld.isNotBlank() timetableItemGroup.isVisible = item.showGroupsInPlan && lesson.group.isNotBlank() timetableItemTeacher.visibility = VISIBLE } From 515a3973b74048daba05783609b5530b59279938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 28 Oct 2022 11:09:38 +0200 Subject: [PATCH 037/545] Use text color, font face and red dot to differentiate unread messages (#2027) --- .../modules/message/tab/MessageTabAdapter.kt | 33 ++++++++++++++----- app/src/main/res/drawable/ic_circle.xml | 10 ++++++ app/src/main/res/layout/item_message.xml | 22 +++++++++++-- 3 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/drawable/ic_circle.xml diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 55f03ef84..234d17eb2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -1,15 +1,18 @@ package io.github.wulkanowy.ui.modules.message.tab +import android.content.res.ColorStateList import android.graphics.Typeface import android.view.LayoutInflater import android.view.ViewGroup import android.widget.CompoundButton import androidx.core.view.isVisible +import androidx.core.widget.ImageViewCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.databinding.ItemMessageBinding import io.github.wulkanowy.databinding.ItemMessageChipsBinding +import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject @@ -85,21 +88,35 @@ class MessageTabAdapter @Inject constructor() : val message = item.message with(holder.binding) { - val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL + val normalFont = Typeface.create("sans-serif", Typeface.NORMAL) + val boldFont = Typeface.create("sans-serif-black", Typeface.NORMAL) + + val primaryColor = root.context.getThemeAttrColor(android.R.attr.textColorPrimary) + val secondaryColor = root.context.getThemeAttrColor(android.R.attr.textColorSecondary) + + val currentFont = if (message.unread) boldFont else normalFont + val currentTextColor = if (message.unread) primaryColor else secondaryColor with(messageItemAuthor) { text = message.correspondents - setTypeface(null, style) + setTextColor(currentTextColor) + typeface = currentFont } - messageItemSubject.run { + with(messageItemSubject) { text = message.subject.ifBlank { context.getString(R.string.message_no_subject) } - setTypeface(null, style) + setTextColor(currentTextColor) + typeface = currentFont } - messageItemDate.run { + with(messageItemDate) { text = message.date.toFormattedString() - setTypeface(null, style) + setTextColor(currentTextColor) + typeface = currentFont } - messageItemAttachmentIcon.isVisible = message.hasAttachments + with(messageItemAttachmentIcon) { + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor)) + isVisible = message.hasAttachments + } + messageItemUnreadIndicator.isVisible = message.unread root.setOnClickListener { holder.bindingAdapterPosition.let { @@ -111,7 +128,7 @@ class MessageTabAdapter @Inject constructor() : root.setOnLongClickListener { onLongItemClickListener(item) - return@setOnLongClickListener true + true } with(messageItemCheckbox) { diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml new file mode 100644 index 000000000..d2932fe62 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index c25faacc8..39fbaad01 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -30,6 +30,7 @@ android:layout_marginEnd="10dp" android:ellipsize="end" android:singleLine="true" + android:textColor="?android:textColorSecondary" android:textSize="15sp" app:layout_constraintEnd_toStartOf="@+id/messageItemDate" app:layout_constraintStart_toEndOf="@id/messageItemCheckbox" @@ -40,10 +41,13 @@ android:id="@+id/messageItemDate" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="8dp" android:gravity="end" + android:textColor="?android:textColorSecondary" android:textSize="13sp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/messageItemUnreadIndicator" app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginEnd="0dp" tools:text="@tools:sample/date/ddmmyy" /> + + From c5e2b18695c2a1f58afd407d399d927d03573dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 28 Oct 2022 11:10:05 +0200 Subject: [PATCH 038/545] Fix SSL certificate out-of-date detection (#2028) --- .../java/io/github/wulkanowy/utils/ExceptionExtension.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt index 43cecd400..a4c2537ac 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -15,16 +15,17 @@ import java.net.ConnectException import java.net.SocketException import java.net.SocketTimeoutException import java.net.UnknownHostException +import java.security.cert.CertPathValidatorException import java.security.cert.CertificateExpiredException import java.security.cert.CertificateNotYetValidException import javax.net.ssl.SSLHandshakeException fun Resources.getErrorString(error: Throwable): String = when (error) { is UnknownHostException -> R.string.error_no_internet + is ConnectException, is SocketException, is SocketTimeoutException, is InterruptedIOException, - is ConnectException, is StreamResetException -> R.string.error_timeout is NotLoggedInException -> R.string.error_login_failed is PasswordChangeRequiredException -> R.string.error_password_change_required @@ -42,10 +43,10 @@ fun Resources.getErrorString(error: Throwable): String = when (error) { fun Throwable.isShouldBeReported(): Boolean = when (this) { is UnknownHostException, + is ConnectException, is SocketException, is SocketTimeoutException, is InterruptedIOException, - is ConnectException, is StreamResetException, is ServiceUnavailableException, is FeatureDisabledException, @@ -70,5 +71,6 @@ private fun Throwable?.isCausedByCertificateNotValidNow(): Boolean { private fun Throwable?.isCertificateNotValidNow(): Boolean { val isNotYetValid = this is CertificateNotYetValidException val isExpired = this is CertificateExpiredException - return isNotYetValid || isExpired + val isInvalidPath = this is CertPathValidatorException + return isNotYetValid || isExpired || isInvalidPath } From ffd5addadb3f1250f438ad3cea2e2889649e31b8 Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:10:35 +0200 Subject: [PATCH 039/545] Add Crowdin badges to README (#2025) --- README.cs.md | 1 + README.de.md | 1 + README.en.md | 1 + README.md | 1 + README.sk.md | 1 + 5 files changed, 5 insertions(+) diff --git a/README.cs.md b/README.cs.md index 5c1e5ea71..0d6da9c36 100644 --- a/README.cs.md +++ b/README.cs.md @@ -13,6 +13,7 @@ [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl) Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče diff --git a/README.de.md b/README.de.md index e03557a06..9c25bad65 100644 --- a/README.de.md +++ b/README.de.md @@ -13,6 +13,7 @@ [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl) Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern diff --git a/README.en.md b/README.en.md index 1ac2a6721..708d30bdb 100644 --- a/README.en.md +++ b/README.en.md @@ -13,6 +13,7 @@ [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl) Unofficial android VULCAN UONET+ register client for both students and their parents diff --git a/README.md b/README.md index e7c7d4c5e..86b83552a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl) Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica diff --git a/README.sk.md b/README.sk.md index 2f3ba41dd..d318936aa 100644 --- a/README.sk.md +++ b/README.sk.md @@ -13,6 +13,7 @@ [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) +[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl) Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov From b269360ecb852bfaf9eb3b97c762e7e7c689c9e1 Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Sun, 30 Oct 2022 03:00:39 +0100 Subject: [PATCH 040/545] Langs placement in README adjustments (#2029) --- README.cs.md | 10 ++-------- README.de.md | 8 +------- README.en.md | 8 +------- README.md | 8 +------- README.sk.md | 10 ++-------- 5 files changed, 7 insertions(+), 37 deletions(-) diff --git a/README.cs.md b/README.cs.md index 0d6da9c36..2b0dc12ea 100644 --- a/README.cs.md +++ b/README.cs.md @@ -1,10 +1,4 @@ -[English version of README](README.en.md) - -[Deutsche Version von README](README.de.md) - -[Polska wersja README](README.md) - -[Slovenská verzia README](README.sk.md) +Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) # Wulkanowy @@ -58,7 +52,7 @@ Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGal Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání -## Postaveno s +## Postaveno s pomocí * [Wulkanowy SDK](https://github.com/wulkanowy/sdk) diff --git a/README.de.md b/README.de.md index 9c25bad65..6df10ecd0 100644 --- a/README.de.md +++ b/README.de.md @@ -1,10 +1,4 @@ -[Polska wersja README](README.md) - -[English version of README](README.en.md) - -[Česká verze README](README.cs.md) - -[Slovenská verzia README](README.sk.md) +[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) # Wulkanowy diff --git a/README.en.md b/README.en.md index 708d30bdb..417b74de0 100644 --- a/README.en.md +++ b/README.en.md @@ -1,10 +1,4 @@ -[Polska wersja README](README.md) - -[Deutsche Version von README](README.de.md) - -[Česká verze README](README.cs.md) - -[Slovenská verzia README](README.sk.md) +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) # Wulkanowy diff --git a/README.md b/README.md index 86b83552a..75b6cfca2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,4 @@ -[English version of README](README.en.md) - -[Deutsche Version von README](README.de.md) - -[Česká verze README](README.cs.md) - -[Slovenská verzia README](README.sk.md) +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md) # Wulkanowy diff --git a/README.sk.md b/README.sk.md index d318936aa..240f8835c 100644 --- a/README.sk.md +++ b/README.sk.md @@ -1,10 +1,4 @@ -[English version of README](README.en.md) - -[Deutsche Version von README](README.de.md) - -[Polska wersja README](README.md) - -[Česká verze README](README.cs.md) +[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia # Wulkanowy @@ -58,7 +52,7 @@ Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGa Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie -## Postavené s +## Postavené s pomocou * [Wulkanowy SDK](https://github.com/wulkanowy/sdk) From d924902dacac95123477dd6410c62430785aaa3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:49:56 +0000 Subject: [PATCH 041/545] Bump CircularImageView from 4.2.0 to 4.3.0 (#2036) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6211db2d8..d7fcfa8f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -209,7 +209,7 @@ dependencies { implementation "com.google.android.material:material:1.7.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.2.0' + implementation 'com.github.lopspower:CircularImageView:4.3.0' implementation "androidx.work:work-runtime-ktx:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" From 4113bd9b538428cb6465cc3380b4b4d2f25ba9b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:50:17 +0000 Subject: [PATCH 042/545] Bump agconnect-crash from 1.7.2.300 to 1.7.3.300 (#2035) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d7fcfa8f7..c250e7ead 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:21.3.0' hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.2.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 86fe2b61cb9c158e53682974c11484343141e003 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:50:37 +0000 Subject: [PATCH 043/545] Bump agcp from 1.7.2.300 to 1.7.3.300 (#2034) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5131796bb..fa66d9304 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.huawei.agconnect:agcp:1.7.2.300' + classpath 'com.huawei.agconnect:agcp:1.7.3.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" From 02cd4e4e06850b95a845b45db8c2b7f0194061ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:50:59 +0000 Subject: [PATCH 044/545] Bump sonarqube-gradle-plugin from 3.4.0.2513 to 3.5.0.2730 (#2033) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fa66d9304..3773dbc38 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From 21fe20924643fa4c6db0aa05b2bbee30d48ba071 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:51:28 +0000 Subject: [PATCH 045/545] Bump firebase-bom from 31.0.1 to 31.0.2 (#2032) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c250e7ead..a8f68884e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:31.0.1') + playImplementation platform('com.google.firebase:firebase-bom:31.0.2') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 62b7d42a73d0f422b02995f049449966020f0df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 1 Nov 2022 20:57:05 +0100 Subject: [PATCH 046/545] New Crowdin updates (#1966) --- app/src/main/res/values-de/strings.xml | 14 +- .../res/values-es-rES/preferences_values.xml | 65 ++ app/src/main/res/values-es-rES/strings.xml | 715 ++++++++++++++++++ app/src/main/res/values-uk/strings.xml | 38 +- 4 files changed, 806 insertions(+), 26 deletions(-) create mode 100644 app/src/main/res/values-es-rES/preferences_values.xml create mode 100644 app/src/main/res/values-es-rES/strings.xml diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4dfeee4f6..7b544258a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -632,10 +632,10 @@ Antwort mit Nachrichtenhistorie Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind Unterstützung - Privacy Policy - Agreements - Consent to processing of data related to ads - Show ads in app + Datenschutz-Bestimmungen + Vereinbarungen + Zustimmung zur Verarbeitung von Daten im Zusammenhang mit Anzeigen + Anzeigen in der App anzeigen Einzelanzeige ansehen, um Projekt zu unterstützen Einwilligung in die Datenverarbeitung Um eine Anzeige zu sehen, müssen Sie mit den Datenverarbeitungsbedingungen unserer Datenschutzerklärung einverstanden sein @@ -647,9 +647,9 @@ Sie können Ihre Wahl jederzeit in den App-Einstellungen ändern. Wir verwenden Ihre Daten, um auf Sie zugeschnittene Anzeigen anzuzeigen oder unter Verwendung weniger Ihrer Daten nicht personalisierte Werbung anzuzeigen. Bitte lesen Sie unsere Datenschutzerklärung für Details Personalisierte Werbung keine personalisierte Werbung - I am over 18 years old - Yes, personalized ads - Yes, non-personalized ads + Ich bin über 18 Jahre alt + Ja, personalisierte Werbung + Ja, nicht personalisierte Werbung Erweitert Aussehen & Verhalten Benachrichtigungen diff --git a/app/src/main/res/values-es-rES/preferences_values.xml b/app/src/main/res/values-es-rES/preferences_values.xml new file mode 100644 index 000000000..ac2b6e9e5 --- /dev/null +++ b/app/src/main/res/values-es-rES/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-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml new file mode 100644 index 000000000..aecc720b7 --- /dev/null +++ b/app/src/main/res/values-es-rES/strings.xml @@ -0,0 +1,715 @@ + + + + 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 + + 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 + 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 → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous 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 + + 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 + Only unread + Only with attachments + Read: %s + + %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 + + 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 + + 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 + + No lessons + 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 + 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 + + 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 f90e78e7d..2c104ecbf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -77,9 +77,9 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову - Application support - Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time - Enable ads + Підтримка додатків + Вам подобається цей додаток? Підтримуйте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете будь-коли вимкнути + Увімкнути рекламу Оцінка %d семестр @@ -97,7 +97,7 @@ Передбачувана оцінка Розрахована середня оцінка Як працює \"Розрахована середня оцінка\"? - 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 + Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів.Це дозволяє дізнатися приблизну кінцеву середню оцінку.Вона розраховується способом, обраним користувачем у налаштуваннях програми.Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку.Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки.Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх Як працює \"Підсумкова середня оцінка\"? Підсумкове середнє значення - це середнє арифметичне, обчислене з усіх наявних наразі підсумкових оцінок у даному семестрі. \n\nСхема обчислення складається з таких кроків:\n1. Сумування підсумкових оцінок, виставленних викладачами\n2. Ділення на кількість предметів, з яких виставлені ці оцінки Підсумкова середня оцінка @@ -297,10 +297,10 @@ Перемістити до кошика Видалити назавжди Лист було успішно видалено - student - parent - guardian - employee + студент + батькові + опікун + працівник Поділитись Друк Тема @@ -720,10 +720,10 @@ Відповісти з історією повідомлень Вилічити середню аритметичну, якщо оцінка немає вартості Підтримка - Privacy Policy - Agreements - Consent to processing of data related to ads - Show ads in app + Політика конфіденційності + Угоди + Згода на обробку даних, пов’язаних з оголошеннями + Показувати рекламу в додатку Подивіться одну рекламу для підтримки проєкту Згода в обробці даних Щоб переглянути рекламу, ви повинні погодитися з умовами обробки даних нашої Політики конфіденційності @@ -731,13 +731,13 @@ Політика конфіденційності Реклама завантажується Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам - 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 + Чи можемо ми використовувати ваші дані для відбивання реклами? + Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для відбивання оголошень, адаптованої до вас або, використовуючи менше ваших даних, відбивати неперсоналізовані оголошення. Перегляньте нашу Політику конфіденційності для деталей + Персоналізовані оголошення + Неперсоналізована реклама + Мені більше 18 років + Так, персоналізована реклама + Так, неперсоналізована реклама Додатково Вигляд та поведінка Сповіщення From 50b6d380b6e7c9c5537ddcce698e9b8c00c9db0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 2 Nov 2022 16:44:05 +0100 Subject: [PATCH 047/545] Fix unexpected error in support ad when no internet (#2030) --- .../ui/modules/settings/ads/AdsPresenter.kt | 21 ++++++++++++----- .../io/github/wulkanowy/utils/AdsHelper.kt | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt index 772d616d7..28c98e3c3 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt @@ -31,13 +31,22 @@ class AdsPresenter @Inject constructor( view?.showLoadingSupportAd(true) presenterScope.launch { runCatching { adsHelper.getSupportAd() } - .onFailure(errorHandler::dispatch) - .onSuccess { it?.let { view?.showAd(it) } } + .onFailure { + errorHandler.dispatch(it) - view?.run { - showLoadingSupportAd(false) - showWatchAdOncePerVisit(true) - } + view?.run { + showLoadingSupportAd(false) + showWatchAdOncePerVisit(false) + } + } + .onSuccess { + it?.let { view?.showAd(it) } + + view?.run { + showLoadingSupportAd(false) + showWatchAdOncePerVisit(true) + } + } } } diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt index c536e2218..d5f65b46d 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -1,8 +1,12 @@ package io.github.wulkanowy.utils import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build import android.os.Bundle import android.view.View +import androidx.core.content.getSystemService import com.google.ads.mediation.admob.AdMobAdapter import com.google.android.gms.ads.* import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd @@ -10,6 +14,7 @@ import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoa import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.BuildConfig import io.github.wulkanowy.data.repositories.PreferencesRepository +import java.net.UnknownHostException import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -28,6 +33,10 @@ class AdsHelper @Inject constructor( } suspend fun getSupportAd(): RewardedInterstitialAd? { + if (!context.isInternetConnected()) { + throw UnknownHostException() + } + val extra = Bundle().apply { putString("npa", "1") } val adRequest = AdRequest.Builder() .apply { @@ -84,4 +93,18 @@ class AdsHelper @Inject constructor( } } +@Suppress("DEPRECATION") +private fun Context.isInternetConnected(): Boolean { + val connectivityManager = getSystemService() ?: return false + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val currentNetwork = connectivityManager.activeNetwork + val networkCapabilities = connectivityManager.getNetworkCapabilities(currentNetwork) + + networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true + } else { + connectivityManager.activeNetworkInfo?.isConnected == true + } +} + data class AdBanner(val view: View) From 1257dc63d3c081ec75dffebfebf2fdc14dacd18f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:21:17 +0000 Subject: [PATCH 048/545] Bump runner from 1.4.0 to 1.5.1 (#2047) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a8f68884e..7142d92fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -264,7 +264,7 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation 'org.robolectric:robolectric:4.9' - testImplementation "androidx.test:runner:1.4.0" + testImplementation "androidx.test:runner:1.5.1" testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "androidx.test:core:1.4.0" testImplementation "androidx.room:room-testing:$room" From 66ff14f719bf021b83b43e09e5bca726a843d088 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:21:35 +0000 Subject: [PATCH 049/545] Bump agcp from 1.7.3.300 to 1.7.3.301 (#2046) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3773dbc38..ccce1c433 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.huawei.agconnect:agcp:1.7.3.300' + classpath 'com.huawei.agconnect:agcp:1.7.3.301' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" From fded5007c14ecbec95680e5b9c5960cab3e77927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:22:25 +0000 Subject: [PATCH 050/545] Bump firebase-bom from 31.0.2 to 31.0.3 (#2041) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7142d92fb..40f571714 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:31.0.2') + playImplementation platform('com.google.firebase:firebase-bom:31.0.3') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 885319a8854f22cfa91dd097db8492599d09fd25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:36:20 +0000 Subject: [PATCH 051/545] Bump hilt_version from 2.44 to 2.44.1 (#2044) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ccce1c433..6f3ac4d63 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.7.20' about_libraries = '10.5.1' - hilt_version = "2.44" + hilt_version = "2.44.1" } repositories { mavenCentral() From d6385e8cdd06b3636568b32aab5c0064e8bbd8cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 20:31:02 +0000 Subject: [PATCH 052/545] Bump core from 1.4.0 to 1.5.0 (#2045) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 40f571714..1edcc753e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -266,7 +266,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.9' testImplementation "androidx.test:runner:1.5.1" testImplementation "androidx.test.ext:junit:1.1.3" - testImplementation "androidx.test:core:1.4.0" + testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.room:room-testing:$room" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" From b8296ac02f94e17e972c74980bcf480352abaef6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 20:31:26 +0000 Subject: [PATCH 053/545] Bump kotlin_version from 1.7.20 to 1.7.21 (#2042) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6f3ac4d63..e8e1052b6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.7.20' + kotlin_version = '1.7.21' about_libraries = '10.5.1' hilt_version = "2.44.1" } From 4d49e956b881125354b86228a12c50e49ea6fd8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 20:48:12 +0000 Subject: [PATCH 054/545] Bump junit from 1.1.3 to 1.1.4 (#2043) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1edcc753e..11f055ea8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -265,7 +265,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.9' testImplementation "androidx.test:runner:1.5.1" - testImplementation "androidx.test.ext:junit:1.1.3" + testImplementation "androidx.test.ext:junit:1.1.4" testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.room:room-testing:$room" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" From db4f172fb83bd4ca4a3f7219ae5e6f40f87d5727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 16 Nov 2022 12:54:55 +0100 Subject: [PATCH 055/545] Fix unread status in sent messages (#2048) --- app/build.gradle | 2 +- .../52.json | 2421 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../wulkanowy/data/db/entities/Message.kt | 6 + .../wulkanowy/data/mappers/MessageMapper.kt | 4 +- .../debug/notification/mock/message.kt | 2 + .../message/preview/MessagePreviewAdapter.kt | 16 +- app/src/main/res/values/strings.xml | 1 + .../repositories/MessageRepositoryTest.kt | 6 +- 9 files changed, 2451 insertions(+), 10 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json diff --git a/app/build.gradle b/app/build.gradle index 11f055ea8..99672f1f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:2840d9d6d0" + implementation "io.github.wulkanowy:sdk:701016eda2" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json new file mode 100644 index 000000000..129d1917b --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/52.json @@ -0,0 +1,2421 @@ +{ + "formatVersion": 1, + "database": { + "version": 52, + "identityHash": "8742176f26afcc81279d4a073dca2949", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "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": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mailboxes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalKey` TEXT NOT NULL, `fullName` TEXT NOT NULL, `userName` TEXT NOT NULL, `userLoginId` INTEGER 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": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "userName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "userLoginId", + "affinity": "INTEGER", + "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": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8742176f26afcc81279d4a073dca2949')" + ] + } +} \ 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 15b38805b..e61ffe844 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 @@ -47,6 +47,7 @@ import javax.inject.Singleton AutoMigration(from = 44, to = 45), AutoMigration(from = 46, to = 47), AutoMigration(from = 47, to = 48), + AutoMigration(from = 51, to = 52), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -55,7 +56,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 51 + const val VERSION_SCHEMA = 52 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt index 77874e03d..323b00bec 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt @@ -29,6 +29,12 @@ data class Message( var unread: Boolean, + @ColumnInfo(name = "read_by") + val readBy: Int?, + + @ColumnInfo(name = "unread_by") + val unreadBy: Int?, + @ColumnInfo(name = "has_attachments") val hasAttachments: Boolean ) : Serializable { diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 2e7967f0e..c329607f4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -16,7 +16,9 @@ fun List.mapToEntities(mailbox: Mailbox) = map { date = it.dateZoned.toInstant(), folderId = it.folderId, unread = it.unread, - hasAttachments = it.hasAttachments + unreadBy = it.unreadBy, + readBy = it.readBy, + hasAttachments = it.hasAttachments, ).apply { content = it.content.orEmpty() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt index 6ff26162b..e31bd84a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt @@ -22,6 +22,8 @@ private fun generateMessage(sender: String, subject: String) = Message( date = Instant.now(), folderId = 0, unread = true, + readBy = 2, + unreadBy = 2, hasAttachments = false, messageGlobalKey = "", correspondents = sender, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index 3c1c53d39..d3c6b95c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.message.preview -import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -74,15 +73,20 @@ class MessagePreviewAdapter @Inject constructor() : } } - @SuppressLint("SetTextI18n") private fun bindMessage(holder: MessageViewHolder, message: Message) { val context = holder.binding.root.context + val recipientCount = (message.unreadBy ?: 0) + (message.readBy ?: 0) + val isReceived = message.unreadBy == null - val readTextValue = when { - !message.unread -> R.string.all_yes - else -> R.string.all_no + val readText = when { + recipientCount > 1 -> { + context.getString(R.string.message_read_by, message.readBy, recipientCount) + } + message.readBy == 1 || (isReceived && !message.unread) -> { + context.getString(R.string.message_read, context.getString(R.string.all_yes)) + } + else -> context.getString(R.string.message_read, context.getString(R.string.all_no)) } - val readText = context.getString(R.string.message_read, context.getString(readTextValue)) with(holder.binding) { messagePreviewSubject.text = message.subject.ifBlank { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 48376d216..e65a27fcc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -304,6 +304,7 @@ Only unread Only with attachments Read: %s + Read by: %1$d of %2$d people %1$d message %1$d messages diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 4efc9c60a..9fc83a23b 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -193,7 +193,9 @@ class MessageRepositoryTest { date = Instant.EPOCH, folderId = 1, unread = unread, - hasAttachments = false + readBy = 1, + unreadBy = 1, + hasAttachments = false, ).apply { this.content = content } @@ -209,6 +211,8 @@ class MessageRepositoryTest { dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC), folderId = 1, unread = true, + readBy = 1, + unreadBy = 1, hasAttachments = false, ) } From 51a1097bb4af2373bcd14ac8189e66f1cb606229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 16 Nov 2022 13:46:47 +0100 Subject: [PATCH 056/545] Add mailbox chooser to messages (#2002) --- .../53.json | 2439 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../wulkanowy/data/db/dao/MailboxDao.kt | 8 +- .../wulkanowy/data/db/dao/MessagesDao.kt | 3 + .../wulkanowy/data/db/entities/Mailbox.kt | 11 +- .../wulkanowy/data/db/entities/Message.kt | 3 + .../data/db/migrations/Migration53.kt | 57 + .../wulkanowy/data/mappers/MailboxMapper.kt | 4 +- .../wulkanowy/data/mappers/MessageMapper.kt | 7 +- .../data/repositories/MailboxRepository.kt | 85 - .../data/repositories/MessageRepository.kt | 67 +- .../data/repositories/RecipientRepository.kt | 24 +- .../messages/GetMailboxByStudentUseCase.kt | 52 + .../notifications/NewMessageNotification.kt | 1 - .../services/sync/works/MessageWork.kt | 6 +- .../services/sync/works/RecipientWork.kt | 15 +- .../modules/dashboard/DashboardPresenter.kt | 3 +- .../debug/notification/mock/message.kt | 1 + .../mailboxchooser/MailboxChooserAdapter.kt | 81 + .../mailboxchooser/MailboxChooserDialog.kt | 75 + .../mailboxchooser/MailboxChooserItem.kt | 9 + .../mailboxchooser/MailboxChooserPresenter.kt | 38 + .../mailboxchooser/MailboxChooserView.kt | 13 + .../preview/MessagePreviewPresenter.kt | 6 +- .../message/send/SendMessageActivity.kt | 16 + .../message/send/SendMessagePresenter.kt | 107 +- .../modules/message/send/SendMessageView.kt | 1 + .../modules/message/tab/MessageTabAdapter.kt | 32 +- .../modules/message/tab/MessageTabDataItem.kt | 1 + .../modules/message/tab/MessageTabFragment.kt | 20 + .../message/tab/MessageTabPresenter.kt | 39 +- .../ui/modules/message/tab/MessageTabView.kt | 3 + .../io/github/wulkanowy/utils/RefreshUtils.kt | 5 +- .../main/res/layout/activity_send_message.xml | 20 +- .../res/layout/dialog_mailbox_chooser.xml | 40 + .../main/res/layout/item_mailbox_chooser.xml | 43 + .../main/res/layout/item_message_chips.xml | 23 +- app/src/main/res/values/strings.xml | 2 + .../io/github/wulkanowy/TestEnityCreator.kt | 4 +- .../repositories/MessageRepositoryTest.kt | 24 +- .../GetMailboxByStudentUseCaseTest.kt} | 53 +- 41 files changed, 3209 insertions(+), 235 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt create mode 100644 app/src/main/res/layout/dialog_mailbox_chooser.xml create mode 100644 app/src/main/res/layout/item_mailbox_chooser.xml rename app/src/test/java/io/github/wulkanowy/{data/repositories/MailboxRepositoryTest.kt => domain/GetMailboxByStudentUseCaseTest.kt} (77%) diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json new file mode 100644 index 000000000..985617872 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/53.json @@ -0,0 +1,2439 @@ +{ + "formatVersion": 1, + "database": { + "version": 53, + "identityHash": "1dc96a366125ec9f8567da87cdc9c863", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "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": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "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": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dc96a366125ec9f8567da87cdc9c863')" + ] + } +} \ 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 e61ffe844..792611a81 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 @@ -56,7 +56,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 52 + const val VERSION_SCHEMA = 53 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -106,6 +106,7 @@ abstract class AppDatabase : RoomDatabase() { Migration49(), Migration50(), Migration51(), + Migration53(), ) fun newInstance( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt index c44ecd0c2..084192a07 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MailboxDao.kt @@ -3,12 +3,16 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query import io.github.wulkanowy.data.db.entities.Mailbox +import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @Dao interface MailboxDao : BaseDao { - @Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ") - suspend fun loadAll(userLoginId: Int): List + @Query("SELECT * FROM Mailboxes WHERE email = :email") + suspend fun loadAll(email: String): List + + @Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId") + fun loadAll(email: String, symbol: String, schoolId: String): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt index 8c730c9bc..1709f7636 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt @@ -16,4 +16,7 @@ interface MessagesDao : BaseDao { @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") fun loadAll(mailboxKey: String, folder: Int): Flow> + + @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC") + fun loadAll(folder: Int, email: String): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt index 7c08e481d..e65e213dd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Mailbox.kt @@ -1,20 +1,27 @@ package io.github.wulkanowy.data.db.entities +import android.os.Parcelable import androidx.room.Entity import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize +@Parcelize @Entity(tableName = "Mailboxes") data class Mailbox( @PrimaryKey val globalKey: String, + + val email: String, + val symbol: String, + val schoolId: String, + val fullName: String, val userName: String, - val userLoginId: Int, val studentName: String, val schoolNameShort: String, val type: MailboxType, -) +) : java.io.Serializable, Parcelable enum class MailboxType { STUDENT, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt index 323b00bec..d1356b33d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Message.kt @@ -9,6 +9,9 @@ import java.time.Instant @Entity(tableName = "Messages") data class Message( + @ColumnInfo(name = "email") + val email: String, + @ColumnInfo(name = "message_global_key") val messageGlobalKey: String, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt new file mode 100644 index 000000000..12624a51a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt @@ -0,0 +1,57 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration53 : Migration(52, 53) { + + override fun migrate(database: SupportSQLiteDatabase) { + createMailboxTable(database) + recreateMessagesTable(database) + } + + private fun createMailboxTable(database: SupportSQLiteDatabase) { + database.execSQL("DROP TABLE IF EXISTS Mailboxes") + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Mailboxes` ( + `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`) + )""".trimIndent() + ) + } + + private fun recreateMessagesTable(database: SupportSQLiteDatabase) { + database.execSQL("DROP TABLE IF EXISTS Messages") + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `Messages` ( + `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 + )""".trimIndent() + ) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt index 2ccca1b90..0cf547770 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MailboxMapper.kt @@ -10,9 +10,11 @@ fun List.mapToEntities(student: Student) = map { globalKey = it.globalKey, fullName = it.fullName, userName = it.userName, - userLoginId = student.userLoginId, studentName = it.studentName, schoolNameShort = it.schoolNameShort, type = MailboxType.valueOf(it.type.name), + email = student.email, + symbol = student.symbol, + schoolId = student.schoolSymbol, ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index c329607f4..8825c5744 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -6,10 +6,13 @@ import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient -fun List.mapToEntities(mailbox: Mailbox) = map { +fun List.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List) = map { Message( messageGlobalKey = it.globalKey, - mailboxKey = mailbox.globalKey, + mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> + box.fullName == it.mailbox + }?.globalKey!!, + email = student.email, messageId = it.id, correspondents = it.correspondents, subject = it.subject.trim(), diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt deleted file mode 100644 index c571937ad..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MailboxRepository.kt +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.wulkanowy.data.repositories - -import io.github.wulkanowy.data.db.dao.MailboxDao -import io.github.wulkanowy.data.db.entities.Mailbox -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MailboxRepository @Inject constructor( - private val mailboxDao: MailboxDao, - private val sdk: Sdk, - private val refreshHelper: AutoRefreshHelper, -) { - private val cacheKey = "mailboxes" - - suspend fun refreshMailboxes(student: Student) { - val new = sdk.init(student).getMailboxes().mapToEntities(student) - val old = mailboxDao.loadAll(student.userLoginId) - - mailboxDao.deleteAll(old uniqueSubtract new) - mailboxDao.insertAll(new uniqueSubtract old) - - refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) - } - - suspend fun getMailbox(student: Student): Mailbox { - val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) - val mailboxes = mailboxDao.loadAll(student.userLoginId) - val mailbox = mailboxes.filterByStudent(student) - - return if (isExpired || mailbox == null) { - refreshMailboxes(student) - val newMailbox = mailboxDao.loadAll(student.userLoginId).filterByStudent(student) - - requireNotNull(newMailbox) { - "Mailbox for ${student.userName} - ${student.studentName} not found! Saved mailboxes: $mailboxes" - } - - newMailbox - } else mailbox - } - - private fun List.filterByStudent(student: Student): Mailbox? { - val normalizedStudentName = student.studentName.normalizeStudentName() - - return find { - it.studentName.normalizeStudentName() == normalizedStudentName - } ?: singleOrNull { - it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() - } ?: singleOrNull { - it.studentName.getUnauthorizedVersion() == normalizedStudentName - } - } - - private fun String.normalizeStudentName(): String { - return trim().split(" ") - .filter { it.isNotBlank() } - .joinToString(" ") { part -> - part.lowercase().replaceFirstChar { it.uppercase() } - } - } - - private fun String.getFirstAndLastPart(): String { - val parts = normalizeStudentName().split(" ") - - val endParts = parts.filterIndexed { i, _ -> - i == 0 || parts.size - 1 == i - } - return endParts.joinToString(" ") - } - - private fun String.getUnauthorizedVersion(): String { - return normalizeStudentName().split(" ") - .joinToString(" ") { - it.first() + "*".repeat(it.length - 1) - } - } -} 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 e7428762e..f8be4296d 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 @@ -5,6 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R 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.* @@ -15,6 +16,8 @@ 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.pojos.MessageDraft +import io.github.wulkanowy.data.toFirstResult +import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.utils.AutoRefreshHelper @@ -40,16 +43,18 @@ class MessageRepository @Inject constructor( private val refreshHelper: AutoRefreshHelper, private val sharedPrefProvider: SharedPrefProvider, private val json: Json, + private val mailboxDao: MailboxDao, + private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase, ) { private val saveFetchResultMutex = Mutex() - private val cacheKey = "message" + private val messagesCacheKey = "message" + private val mailboxCacheKey = "mailboxes" - @Suppress("UNUSED_PARAMETER") fun getMessages( student: Student, - mailbox: Mailbox, + mailbox: Mailbox?, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false, @@ -58,16 +63,20 @@ class MessageRepository @Inject constructor( isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( - key = getRefreshKey(cacheKey, student, folder) + key = getRefreshKey(messagesCacheKey, mailbox, folder) ) it.isEmpty() || forceRefresh || isExpired }, - query = { messagesDb.loadAll(mailbox.globalKey, folder.id) }, + query = { + if (mailbox == null) { + messagesDb.loadAll(folder.id, student.email) + } else messagesDb.loadAll(mailbox.globalKey, folder.id) + }, fetch = { sdk.init(student).getMessages( folder = Folder.valueOf(folder.name), - mailboxKey = mailbox.globalKey, - ).mapToEntities(mailbox) + mailboxKey = mailbox?.globalKey, + ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email)) }, saveFetchResult = { old, new -> messagesDb.deleteAll(old uniqueSubtract new) @@ -75,7 +84,9 @@ class MessageRepository @Inject constructor( it.isNotified = !notify }) - refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) + refreshHelper.updateLastRefreshTimestamp( + getRefreshKey(messagesCacheKey, mailbox, folder) + ) } ) @@ -90,7 +101,9 @@ class MessageRepository @Inject constructor( Timber.d("Message content in db empty: ${it.message.content.isBlank()}") it.message.unread || it.message.content.isBlank() }, - query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, + query = { + messagesDb.loadMessageWithAttachment(message.messageGlobalKey) + }, fetch = { sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead) }, @@ -113,8 +126,10 @@ class MessageRepository @Inject constructor( } ) - fun getMessagesFromDatabase(mailbox: Mailbox): Flow> { - return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id) + fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow> { + return if (mailbox == null) { + messagesDb.loadAll(RECEIVED.id, student.email) + } else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id) } suspend fun updateMessages(messages: List) { @@ -136,7 +151,7 @@ class MessageRepository @Inject constructor( ) } - suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List) { + suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List) { val firstMessage = messages.first() sdk.init(student).deleteMessages( messages = messages.map { it.messageGlobalKey }, @@ -169,6 +184,34 @@ class MessageRepository @Inject constructor( deleteMessages(student, mailbox, listOf(message)) } + suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource( + mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, + shouldFetch = { + val isExpired = refreshHelper.shouldBeRefreshed( + key = getRefreshKey(mailboxCacheKey, student), + ) + it.isEmpty() || isExpired || forceRefresh + }, + query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, + fetch = { sdk.init(student).getMailboxes().mapToEntities(student) }, + saveFetchResult = { old, new -> + mailboxDao.deleteAll(old uniqueSubtract new) + mailboxDao.insertAll(new uniqueSubtract old) + + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student)) + } + ) + + suspend fun getMailboxByStudent(student: Student): Mailbox? { + val mailbox = getMailboxByStudentUseCase(student) + + return if (mailbox == null) { + getMailboxes(student, forceRefresh = true).toFirstResult() + getMailboxByStudentUseCase(student) + } else mailbox + } + var draftMessage: MessageDraft? get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft)) ?.let { json.decodeFromString(it) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt index e80f028e1..79984ce6d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -33,9 +33,11 @@ class RecipientRepository @Inject constructor( suspend fun getRecipients( student: Student, - mailbox: Mailbox, - type: MailboxType + mailbox: Mailbox?, + type: MailboxType, ): List { + mailbox ?: return emptyList() + val cached = recipientDb.loadAll(type, mailbox.globalKey) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) @@ -47,11 +49,15 @@ class RecipientRepository @Inject constructor( suspend fun getMessageSender( student: Student, - mailbox: Mailbox, - message: Message - ): List = sdk.init(student) - .getMessageReplayDetails(message.messageGlobalKey) - .sender - .let(::listOf) - .mapToEntities(mailbox.globalKey) + mailbox: Mailbox?, + message: Message, + ): List { + mailbox ?: return emptyList() + + return sdk.init(student) + .getMessageReplayDetails(message.messageGlobalKey) + .sender + .let(::listOf) + .mapToEntities(mailbox.globalKey) + } } diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt new file mode 100644 index 000000000..d96794e51 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.domain.messages + +import io.github.wulkanowy.data.db.dao.MailboxDao +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Student +import javax.inject.Inject + +class GetMailboxByStudentUseCase @Inject constructor( + private val mailboxDao: MailboxDao, +) { + + suspend operator fun invoke(student: Student): Mailbox? { + return mailboxDao.loadAll(student.email) + .filterByStudent(student) + } + + private fun List.filterByStudent(student: Student): Mailbox? { + val normalizedStudentName = student.studentName.normalizeStudentName() + + return find { + it.studentName.normalizeStudentName() == normalizedStudentName + } ?: singleOrNull { + it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() + } ?: singleOrNull { + it.studentName.getUnauthorizedVersion() == normalizedStudentName + } + } + + private fun String.normalizeStudentName(): String { + return trim().split(" ") + .filter { it.isNotBlank() } + .joinToString(" ") { part -> + part.lowercase().replaceFirstChar { it.uppercase() } + } + } + + private fun String.getFirstAndLastPart(): String { + val parts = normalizeStudentName().split(" ") + + val endParts = parts.filterIndexed { i, _ -> + i == 0 || parts.size - 1 == i + } + return endParts.joinToString(" ") + } + + private fun String.getUnauthorizedVersion(): String { + return normalizeStudentName().split(" ") + .joinToString(" ") { + it.first() + "*".repeat(it.length - 1) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt index 3b7bcff05..45523d51e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.ui.modules.Destination -import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.getPlural import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index 180568267..c7824e61f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED -import io.github.wulkanowy.data.repositories.MailboxRepository import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewMessageNotification @@ -12,12 +11,11 @@ import javax.inject.Inject class MessageWork @Inject constructor( private val messageRepository: MessageRepository, - private val mailboxRepository: MailboxRepository, private val newMessageNotification: NewMessageNotification, ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { - val mailbox = mailboxRepository.getMailbox(student) + val mailbox = messageRepository.getMailboxByStudent(student) messageRepository.getMessages( student = student, mailbox = mailbox, @@ -26,7 +24,7 @@ class MessageWork @Inject constructor( notify = notify ).waitForResult() - messageRepository.getMessagesFromDatabase(mailbox).first() + messageRepository.getMessagesFromDatabase(student, mailbox).first() .filter { !it.isNotified && it.unread }.let { if (it.isNotEmpty()) newMessageNotification.notify(it, student) messageRepository.updateMessages(it.onEach { message -> message.isNotified = true }) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt index b1322ada3..90b20651d 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt @@ -1,22 +1,23 @@ package io.github.wulkanowy.services.sync.works +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.MailboxRepository +import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.toFirstResult import javax.inject.Inject class RecipientWork @Inject constructor( - private val mailboxRepository: MailboxRepository, + private val messageRepository: MessageRepository, private val recipientRepository: RecipientRepository ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { - mailboxRepository.refreshMailboxes(student) - - val mailbox = mailboxRepository.getMailbox(student) - - recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE) + val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult() + mailboxes.dataOrNull?.forEach { + recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE) + } } } 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 350300937..cb92b0043 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 @@ -25,7 +25,6 @@ class DashboardPresenter @Inject constructor( private val gradeRepository: GradeRepository, private val semesterRepository: SemesterRepository, private val messageRepository: MessageRepository, - private val mailboxRepository: MailboxRepository, private val attendanceSummaryRepository: AttendanceSummaryRepository, private val timetableRepository: TimetableRepository, private val homeworkRepository: HomeworkRepository, @@ -228,7 +227,7 @@ class DashboardPresenter @Inject constructor( private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { flow { val semester = semesterRepository.getCurrentSemester(student) - val mailbox = mailboxRepository.getMailbox(student) + val mailbox = messageRepository.getMailboxByStudent(student) val selectedTiles = preferencesRepository.selectedDashboardTiles val flowSuccess = flowOf(Resource.Success(null)) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt index e31bd84a9..27d8613a3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/message.kt @@ -19,6 +19,7 @@ val debugMessageItems = listOf( private fun generateMessage(sender: String, subject: String) = Message( subject = subject, messageId = 123, + email = "", date = Instant.now(), folderId = 0, unread = true, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt new file mode 100644 index 000000000..59f6d288d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserAdapter.kt @@ -0,0 +1,81 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil.ItemCallback +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.databinding.ItemMailboxChooserBinding +import javax.inject.Inject + +class MailboxChooserAdapter @Inject constructor() : + ListAdapter(Differ) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemMailboxChooserBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class ItemViewHolder( + private val binding: ItemMailboxChooserBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: MailboxChooserItem) { + with(binding) { + mailboxItemName.text = item.mailbox?.getFirstLine() + ?: root.resources.getString(R.string.message_chip_all_mailboxes) + mailboxItemSchool.text = item.mailbox?.getSecondLine() + mailboxItemSchool.isVisible = !item.isAll + + root.setOnClickListener { item.onClickListener(item.mailbox) } + } + } + + private fun Mailbox.getFirstLine() = buildString { + if (studentName.isNotBlank() && studentName != userName) { + append(studentName) + append(" - ") + } + append(userName) + } + + private fun Mailbox.getSecondLine() = buildString { + append(schoolNameShort) + append(" - ") + append(getMailboxType(type)) + } + + private fun getMailboxType(type: MailboxType): String = when (type) { + MailboxType.STUDENT -> R.string.message_mailbox_type_student + MailboxType.PARENT -> R.string.message_mailbox_type_parent + MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian + MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee + MailboxType.UNKNOWN -> null + }.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() } + } + + private object Differ : ItemCallback() { + override fun areItemsTheSame( + oldItem: MailboxChooserItem, + newItem: MailboxChooserItem + ): Boolean { + return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey + } + + override fun areContentsTheSame( + oldItem: MailboxChooserItem, + newItem: MailboxChooserItem + ): Boolean { + return oldItem == newItem + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt new file mode 100644 index 000000000..222412ef1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -0,0 +1,75 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.databinding.DialogMailboxChooserBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import javax.inject.Inject + +@AndroidEntryPoint +class MailboxChooserDialog : BaseDialogFragment(), MailboxChooserView { + + @Inject + lateinit var presenter: MailboxChooserPresenter + + @Inject + lateinit var mailboxAdapter: MailboxChooserAdapter + + companion object { + const val LISTENER_KEY = "mailbox_selected" + const val MAILBOX_KEY = "selected_mailbox" + const val REQUIRED_KEY = "is_mailbox_required" + + fun newInstance(mailboxes: List, isMailboxRequired: Boolean, folder: String) = + MailboxChooserDialog().apply { + arguments = bundleOf( + MAILBOX_KEY to mailboxes.toTypedArray(), + REQUIRED_KEY to isMailboxRequired, + LISTENER_KEY to folder, + ) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, 0) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root + + @Suppress("UNCHECKED_CAST") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + presenter.onAttachView( + view = this, + requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), + mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty() + .toList() as List, + ) + } + + override fun initView() { + binding.accountQuickDialogRecycler.adapter = mailboxAdapter + } + + override fun submitData(items: List) { + mailboxAdapter.submitList(items) + } + + override fun onMailboxSelected(item: Mailbox?) { + setFragmentResult( + requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(), + result = bundleOf(MAILBOX_KEY to item), + ) + dismiss() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt new file mode 100644 index 000000000..6923cf085 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserItem.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox + +data class MailboxChooserItem( + val mailbox: Mailbox? = null, + val isAll: Boolean = false, + val onClickListener: (Mailbox?) -> Unit, +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt new file mode 100644 index 000000000..5bd7c84ab --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserPresenter.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class MailboxChooserPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + fun onAttachView(view: MailboxChooserView, mailboxes: List, requireMailbox: Boolean) { + super.onAttachView(view) + + view.initView() + Timber.i("Mailbox chooser view was initialized") + view.submitData(getMailboxItems(mailboxes, requireMailbox)) + } + + private fun getMailboxItems( + mailboxes: List, + requireMailbox: Boolean, + ): List = buildList { + if (!requireMailbox) { + add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect)) + } + addAll(mailboxes.map { + MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect) + }) + } + + fun onMailboxSelect(item: Mailbox?) { + view?.onMailboxSelected(item) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt new file mode 100644 index 000000000..2e20ee815 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserView.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.ui.modules.message.mailboxchooser + +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.ui.base.BaseView + +interface MailboxChooserView : BaseView { + + fun initView() + + fun submitData(items: List) + + fun onMailboxSelected(item: Mailbox?) +} 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 c011f41f3..fd75f6f3a 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 @@ -6,7 +6,6 @@ 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.MailboxRepository import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -21,7 +20,6 @@ class MessagePreviewPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val mailboxRepository: MailboxRepository, private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { @@ -187,8 +185,8 @@ class MessagePreviewPresenter @Inject constructor( presenterScope.launch { runCatching { val student = studentRepository.getCurrentStudent(decryptPass = true) - val mailbox = mailboxRepository.getMailbox(student) - messageRepository.deleteMessage(student, mailbox, message!!) + val mailbox = messageRepository.getMailboxByStudent(student) + messageRepository.deleteMessage(student, mailbox!!, message!!) } .onFailure { retryCallback = { onMessageDelete() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 334e389e2..b5f687bd4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -19,9 +19,13 @@ import androidx.core.text.toHtml import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.showSoftInput @@ -100,6 +104,7 @@ class SendMessageActivity : BaseActivity + presenter.onMailboxSelected(bundle.getSerializable(MAILBOX_KEY) as? Mailbox) + } } @SuppressLint("ClickableViewAccessibility") @@ -205,6 +213,14 @@ class SendMessageActivity : BaseActivity) { + MailboxChooserDialog.newInstance( + mailboxes = mailboxes, + isMailboxRequired = true, + folder = LISTENER_KEY, + ).show(supportFragmentManager, "chooser") + } + override fun popView() { finish() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index 6c07ee6ad..5ab8f8fc9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -1,15 +1,15 @@ package io.github.wulkanowy.ui.modules.message.send -import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.logResourceStatus -import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.pojos.MessageDraft -import io.github.wulkanowy.data.repositories.* -import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper @@ -28,7 +28,6 @@ class SendMessagePresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val mailboxRepository: MailboxRepository, private val recipientRepository: RecipientRepository, private val preferencesRepository: PreferencesRepository, private val analytics: AnalyticsHelper @@ -36,10 +35,19 @@ class SendMessagePresenter @Inject constructor( private val messageUpdateChannel = Channel() + private var message: Message? = null + private var isReplay: Boolean? = null + + private var mailboxes: List = emptyList() + private var selectedMailbox: Mailbox? = null + fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) { super.onAttachView(view) view.initView() initializeSubjectStream() + this.message = message + this.isReplay = reply + Timber.i("Send message view was initialized") loadData(message, reply) with(view) { @@ -110,16 +118,31 @@ class SendMessagePresenter @Inject constructor( return false } + fun onOpenMailboxChooser() { + view?.showMailboxChooser(mailboxes) + } + + fun onMailboxSelected(mailbox: Mailbox?) { + selectedMailbox = mailbox + + loadData(message, isReplay) + } + private fun loadData(message: Message?, reply: Boolean?) { resourceFlow { val student = studentRepository.getCurrentStudent() - val mailbox = mailboxRepository.getMailbox(student) + + if (selectedMailbox == null && mailboxes.isEmpty()) { + selectedMailbox = messageRepository.getMailboxByStudent(student) + mailboxes = messageRepository.getMailboxes(student, false).toFirstResult() + .dataOrNull.orEmpty() + } Timber.i("Loading recipients started") val recipients = createChips( recipients = recipientRepository.getRecipients( student = student, - mailbox = mailbox, + mailbox = selectedMailbox, type = MailboxType.EMPLOYEE, ) ) @@ -130,7 +153,7 @@ class SendMessagePresenter @Inject constructor( message != null && reply == true -> recipientRepository.getMessageSender( student = student, message = message, - mailbox = mailbox, + mailbox = selectedMailbox, ) else -> emptyList() }.let { createChips(it) } @@ -139,39 +162,42 @@ class SendMessagePresenter @Inject constructor( messageRecipients.size ) - Triple(mailbox, recipients, messageRecipients) + recipients to messageRecipients } .logResourceStatus("load recipients") - .onEach { - when (it) { - is Resource.Loading -> view?.run { - showProgress(true) - showContent(false) - } - is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) -> - view?.run { - setMailbox(getMailboxName(mailbox)) - setRecipients(recipientChips) - if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( - selectedRecipientChips - ) - showContent(true) - } - } - is Resource.Error -> { - view?.showContent(true) - errorHandler.dispatch(it.error) + .onResourceLoading { + view?.run { + showProgress(true) + showContent(false) + } + } + .onResourceNotLoading { + view?.run { showProgress(false) } + } + .onResourceError { + view?.showContent(true) + errorHandler.dispatch(it) + } + .onResourceSuccess { + it.let { (recipientChips, selectedRecipientChips) -> + view?.run { + setMailbox(getMailboxName(selectedMailbox)) + setRecipients(recipientChips) + if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( + selectedRecipientChips + ) + showContent(true) } } - }.onResourceNotLoading { - view?.run { showProgress(false) } - }.launch() + } + .launch() } private fun sendMessage(subject: String, content: String, recipients: List) { + val mailbox = selectedMailbox ?: return + resourceFlow { val student = studentRepository.getCurrentStudent() - val mailbox = mailboxRepository.getMailbox(student) messageRepository.sendMessage( student = student, subject = subject, @@ -222,18 +248,21 @@ class SendMessagePresenter @Inject constructor( } } - private fun getMailboxName(mailbox: Mailbox): String { + private fun getMailboxName(mailbox: Mailbox?): String { + mailbox ?: return "" + + // username - accountType [\n student name - ] (school short name) return buildString { append(mailbox.userName) append(" - ") append(getMailboxType(mailbox.type)) + appendLine() if (mailbox.type == MailboxType.PARENT) { - append(" - ") append(mailbox.studentName) + append(" - ") } - append(" - ") append("(${mailbox.schoolNameShort})") } } @@ -267,9 +296,9 @@ class SendMessagePresenter @Inject constructor( private fun saveDraftMessage() { messageRepository.draftMessage = MessageDraft( - view?.formRecipientsData!!, - view?.formSubjectValue!!, - view?.formContentValue!! + recipients = view?.formRecipientsData!!, + subject = view?.formSubjectValue!!, + content = view?.formContentValue!!, ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt index 1057114b8..e27a09d60 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageView.kt @@ -61,4 +61,5 @@ interface SendMessageView : BaseView { fun getMessageBackupDialogStringWithRecipients(recipients: String): String fun clearDraft() + fun showMailboxChooser(mailboxes: List) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 234d17eb2..6df6153c5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.databinding.ItemMessageBinding import io.github.wulkanowy.databinding.ItemMessageChipsBinding +import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject @@ -19,13 +20,15 @@ import javax.inject.Inject class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() { - var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit = { _, _ -> } + lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit - var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit = {} + lateinit var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit - var onHeaderClickListener: (CompoundButton, Boolean) -> Unit = { _, _ -> } + lateinit var onHeaderClickListener: (CompoundButton, Boolean) -> Unit - var onChangesDetectedListener = {} + lateinit var onMailboxClickListener: () -> Unit + + lateinit var onChangesDetectedListener: () -> Unit private var items = mutableListOf() @@ -49,12 +52,12 @@ class MessageTabAdapter @Inject constructor() : val inflater = LayoutInflater.from(parent.context) return when (MessageItemViewType.values()[viewType]) { - MessageItemViewType.MESSAGE -> ItemViewHolder( - ItemMessageBinding.inflate(inflater, parent, false) - ) MessageItemViewType.FILTERS -> HeaderViewHolder( ItemMessageChipsBinding.inflate(inflater, parent, false) ) + MessageItemViewType.MESSAGE -> ItemViewHolder( + ItemMessageBinding.inflate(inflater, parent, false) + ) } } @@ -69,6 +72,20 @@ class MessageTabAdapter @Inject constructor() : val item = items[position] as MessageTabDataItem.FilterHeader with(holder.binding) { + chipMailbox.text = item.selectedMailbox + ?: root.context.getString(R.string.message_chip_all_mailboxes) + chipMailbox.chipBackgroundColor = ColorStateList.valueOf( + if (item.selectedMailbox == null) { + root.context.getCompatColor(R.color.mtrl_choice_chip_background_color) + } else root.context.getThemeAttrColor(android.R.attr.colorPrimary, 64) + ) + chipMailbox.setTextColor( + if (item.selectedMailbox == null) { + root.context.getThemeAttrColor(android.R.attr.textColorPrimary) + } else root.context.getThemeAttrColor(android.R.attr.colorPrimary) + ) + chipMailbox.setOnClickListener { onMailboxClickListener() } + if (item.onlyUnread == null) { chipUnread.isVisible = false } else { @@ -77,6 +94,7 @@ class MessageTabAdapter @Inject constructor() : chipUnread.setOnCheckedChangeListener(onHeaderClickListener) } chipUnread.isEnabled = item.isEnabled + chipAttachments.isEnabled = item.isEnabled chipAttachments.isChecked = item.onlyWithAttachments chipAttachments.setOnCheckedChangeListener(onHeaderClickListener) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt index 634dfc0e7..c0bd4170e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt @@ -11,6 +11,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) { ) : MessageTabDataItem(MessageItemViewType.MESSAGE) data class FilterHeader( + val selectedMailbox: String?, val onlyUnread: Boolean?, val onlyWithAttachments: Boolean, val isEnabled: Boolean 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 654b0e226..5d608ad3b 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 @@ -10,15 +10,18 @@ import android.widget.CompoundButton import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.core.view.updatePadding +import androidx.fragment.app.setFragmentResultListener import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.databinding.FragmentMessageTabBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.message.MessageFragment +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx @@ -104,6 +107,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag onItemClickListener = presenter::onMessageItemSelected onLongItemClickListener = presenter::onMessageItemLongSelected onHeaderClickListener = ::onChipChecked + onMailboxClickListener = presenter::onMailboxFilterSelected onChangesDetectedListener = ::resetListPosition } @@ -123,6 +127,12 @@ class MessageTabFragment : BaseFragment(R.layout.frag messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } } + + setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle -> + presenter.onMailboxSelected( + mailbox = bundle.getSerializable(MailboxChooserDialog.MAILBOX_KEY) as? Mailbox, + ) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -246,6 +256,16 @@ class MessageTabFragment : BaseFragment(R.layout.frag ) } + override fun showMailboxChooser(mailboxes: List) { + (activity as? MainActivity)?.showDialogFragment( + MailboxChooserDialog.newInstance( + mailboxes = mailboxes, + isMailboxRequired = false, + folder = requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!, + ) + ) + } + override fun hideKeyboard() { activity?.hideSoftInput() } 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 54711a689..ea142db2b 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,9 +1,9 @@ package io.github.wulkanowy.ui.modules.message.tab import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.enums.MessageFolder -import io.github.wulkanowy.data.repositories.MailboxRepository import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -26,7 +26,6 @@ class MessageTabPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val mailboxRepository: MailboxRepository, private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { @@ -36,6 +35,9 @@ class MessageTabPresenter @Inject constructor( private var lastSearchQuery = "" + private var mailboxes: List = emptyList() + private var selectedMailbox: Mailbox? = null + private var messages = emptyList() private val searchChannel = Channel() @@ -122,8 +124,7 @@ class MessageTabPresenter @Inject constructor( runCatching { val student = studentRepository.getCurrentStudent(true) - val mailbox = mailboxRepository.getMailbox(student) - messageRepository.deleteMessages(student, mailbox, messageList) + messageRepository.deleteMessages(student, selectedMailbox, messageList) } .onFailure(errorHandler::dispatch) .onSuccess { view?.showMessagesDeleted() } @@ -202,13 +203,28 @@ class MessageTabPresenter @Inject constructor( } } + fun onMailboxFilterSelected() { + view?.showMailboxChooser(mailboxes) + } + + fun onMailboxSelected(mailbox: Mailbox?) { + selectedMailbox = mailbox + loadData(false) + } + private fun loadData(forceRefresh: Boolean) { Timber.i("Loading $folder message data started") flatResourceFlow { val student = studentRepository.getCurrentStudent() - val mailbox = mailboxRepository.getMailbox(student) - messageRepository.getMessages(student, mailbox, folder, forceRefresh) + + if (selectedMailbox == null && mailboxes.isEmpty()) { + selectedMailbox = messageRepository.getMailboxByStudent(student) + mailboxes = messageRepository.getMailboxes(student, forceRefresh).toFirstResult() + .dataOrNull.orEmpty() + } + + messageRepository.getMessages(student, selectedMailbox, folder, forceRefresh) } .logResourceStatus("load $folder message") .onResourceData { @@ -327,7 +343,16 @@ class MessageTabPresenter @Inject constructor( MessageTabDataItem.FilterHeader( onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT }, onlyWithAttachments = onlyWithAttachments, - isEnabled = !isActionMode + isEnabled = !isActionMode, + selectedMailbox = selectedMailbox?.let { + buildString { + if (it.studentName.isNotBlank() && it.studentName != it.userName) { + append(it.studentName) + append(" - ") + } + append(it.userName) + } + }, ) ) 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 bfa43b209..6ece6621b 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 io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.ui.base.BaseView @@ -46,4 +47,6 @@ interface MessageTabView : BaseView { fun showActionMode(show: Boolean) fun showRecyclerBottomPadding(show: Boolean) + + fun showMailboxChooser(mailboxes: List) } diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index c69fec65c..93e67be01 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder @@ -25,8 +26,8 @@ fun getRefreshKey(name: String, student: Student): String { return "${name}_${student.userLoginId}" } -fun getRefreshKey(name: String, student: Student, folder: MessageFolder): String { - return "${name}_${student.id}_${folder.id}" +fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String { + return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}" } class AutoRefreshHelper @Inject constructor( diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml index 320782bdc..a8041d61c 100644 --- a/app/src/main/res/layout/activity_send_message.xml +++ b/app/src/main/res/layout/activity_send_message.xml @@ -55,17 +55,29 @@ android:id="@+id/sendMessageFrom" android:layout_width="0dp" android:layout_height="58dp" - android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" - android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" + android:layout_marginStart="8dp" + android:background="?selectableItemBackground" android:gravity="center_vertical" + android:paddingStart="8dp" + android:paddingEnd="32dp" android:textSize="18sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/sendMessageFromHint" app:layout_constraintTop_toTopOf="parent" tools:text="Jan Kowalski" /> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_mailbox_chooser.xml b/app/src/main/res/layout/item_mailbox_chooser.xml new file mode 100644 index 000000000..7c93199bc --- /dev/null +++ b/app/src/main/res/layout/item_mailbox_chooser.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_message_chips.xml b/app/src/main/res/layout/item_message_chips.xml index 481a94835..da2e20311 100644 --- a/app/src/main/res/layout/item_message_chips.xml +++ b/app/src/main/res/layout/item_message_chips.xml @@ -1,21 +1,30 @@ - + app:layout_constraintTop_toTopOf="parent" + app:singleLine="true"> + + - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e65a27fcc..e365a00ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -301,6 +301,7 @@ Message does not exist You need to choose at least 1 recipient The message content must be at least 3 characters + Wszystkie skrzynki Only unread Only with attachments Read: %s @@ -324,6 +325,7 @@ %1$d selected Messages deleted + Choose mailbox diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index ff0a53135..84a0cb405 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -27,7 +27,9 @@ fun getMailboxEntity() = Mailbox( globalKey = "v4", fullName = "", userName = "", - userLoginId = 0, + email = "test", + symbol = "powiatwulkanowy", + schoolId = "123456", studentName = "", schoolNameShort = "", type = MailboxType.UNKNOWN, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 9fc83a23b..9a2c22fd6 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories import android.content.Context import io.github.wulkanowy.data.dataOrNull 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.Message @@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.toFirstResult +import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.getMailboxEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk @@ -55,6 +57,12 @@ class MessageRepositoryTest { @MockK private lateinit var sharedPrefProvider: SharedPrefProvider + @MockK + private lateinit var mailboxDao: MailboxDao + + @MockK + private lateinit var getMailboxByStudentUseCase: GetMailboxByStudentUseCase + private val student = getStudentEntity() private val mailbox = getMailboxEntity() @@ -74,26 +82,33 @@ class MessageRepositoryTest { refreshHelper = refreshHelper, sharedPrefProvider = sharedPrefProvider, json = Json, + mailboxDao = mailboxDao, + getMailboxByStudentUseCase = getMailboxByStudentUseCase, ) } @Test fun `get messages when fetched completely new message without notify`() = runBlocking { - every { messageDb.loadAll(any(), any()) } returns flowOf(emptyList()) + coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox) + every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList()) coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf( - getMessageDto() + getMessageDto().copy( + unreadBy = 5, + readBy = 10, + ) ) coEvery { messageDb.deleteAll(any()) } just Runs coEvery { messageDb.insertAll(any()) } returns listOf() - repository.getMessages( + val res = repository.getMessages( student = student, mailbox = mailbox, folder = MessageFolder.RECEIVED, forceRefresh = true, notify = false, - ).toFirstResult().dataOrNull.orEmpty() + ).toFirstResult() + assertEquals(null, res.errorOrNull) coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) } coVerify { messageDb.insertAll(withArg { @@ -187,6 +202,7 @@ class MessageRepositoryTest { ) = Message( messageGlobalKey = "v4", mailboxKey = "", + email = "", correspondents = "", messageId = messageId, subject = "", diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MailboxRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt similarity index 77% rename from app/src/test/java/io/github/wulkanowy/data/repositories/MailboxRepositoryTest.kt rename to app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt index 9198560fe..96a84a5a6 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MailboxRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -1,65 +1,52 @@ -package io.github.wulkanowy.data.repositories +package io.github.wulkanowy.domain import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AutoRefreshHelper import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import java.time.Instant +import kotlin.test.assertEquals +import kotlin.test.assertNull @OptIn(ExperimentalCoroutinesApi::class) -class MailboxRepositoryTest { - - @SpyK - private var sdk = Sdk() +class GetMailboxByStudentUseCaseTest { @MockK private lateinit var mailboxDao: MailboxDao - @MockK - private lateinit var refreshHelper: AutoRefreshHelper - - private lateinit var systemUnderTest: MailboxRepository + private lateinit var systemUnderTest: GetMailboxByStudentUseCase @Before fun setUp() { MockKAnnotations.init(this) - coEvery { refreshHelper.shouldBeRefreshed(any()) } returns false - coEvery { refreshHelper.updateLastRefreshTimestamp(any()) } just Runs coEvery { mailboxDao.deleteAll(any()) } just Runs coEvery { mailboxDao.insertAll(any()) } returns emptyList() coEvery { mailboxDao.loadAll(any()) } returns emptyList() - coEvery { sdk.getMailboxes() } returns emptyList() - systemUnderTest = MailboxRepository( - mailboxDao = mailboxDao, - sdk = sdk, - refreshHelper = refreshHelper, - ) + systemUnderTest = GetMailboxByStudentUseCase(mailboxDao = mailboxDao) } - @Test(expected = IllegalArgumentException::class) + @Test fun `get mailbox that doesn't exist`() = runTest { val student = getStudentEntity( userName = "Stanisław Kowalski", studentName = "Jan Kowalski", ) - coEvery { sdk.getMailboxes() } returns emptyList() + coEvery { mailboxDao.loadAll(any()) } returns emptyList() - systemUnderTest.getMailbox(student) + assertNull(systemUnderTest(student)) } @Test @@ -73,7 +60,7 @@ class MailboxRepositoryTest { expectedMailbox, ) - val selectedMailbox = systemUnderTest.getMailbox(student) + val selectedMailbox = systemUnderTest(student) assertEquals(expectedMailbox, selectedMailbox) } @@ -88,7 +75,7 @@ class MailboxRepositoryTest { expectedMailbox, ) - assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) + assertEquals(expectedMailbox, systemUnderTest(student)) } @Test @@ -102,10 +89,10 @@ class MailboxRepositoryTest { expectedMailbox, ) - assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) + assertEquals(expectedMailbox, systemUnderTest(student)) } - @Test(expected = IllegalArgumentException::class) + @Test fun `get mailbox for not-unique non-authorized student`() = runTest { val student = getStudentEntity( userName = "Stanisław Kowalski", @@ -116,7 +103,7 @@ class MailboxRepositoryTest { getMailboxEntity("Jan Kurowski"), ) - systemUnderTest.getMailbox(student) + assertNull(systemUnderTest(student)) } @Test @@ -130,7 +117,7 @@ class MailboxRepositoryTest { expectedMailbox, ) - assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) + assertEquals(expectedMailbox, systemUnderTest(student)) } @Test @@ -144,7 +131,7 @@ class MailboxRepositoryTest { expectedMailbox, ) - assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) + assertEquals(expectedMailbox, systemUnderTest(student)) } @Test @@ -158,7 +145,7 @@ class MailboxRepositoryTest { expectedMailbox, ) - assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) + assertEquals(expectedMailbox, systemUnderTest(student)) } private fun getMailboxEntity( @@ -167,7 +154,9 @@ class MailboxRepositoryTest { globalKey = "", fullName = "", userName = "", - userLoginId = 123, + email = "", + schoolId = "", + symbol = "", studentName = studentName, schoolNameShort = "", type = MailboxType.STUDENT, From d3e276d6fc79d4806befce5c9f43826b4f4ec4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 16 Nov 2022 20:13:48 +0100 Subject: [PATCH 057/545] New Crowdin updates (#2049) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/src/main/res/values-cs/strings.xml | 5 +- app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values-es-rES/strings.xml | 3 + app/src/main/res/values-pl/strings.xml | 3 + app/src/main/res/values-ru/strings.xml | 41 +++++----- app/src/main/res/values-sk/strings.xml | 5 +- app/src/main/res/values-uk/strings.xml | 25 +++--- app/src/main/res/values/strings.xml | 92 +--------------------- 8 files changed, 55 insertions(+), 122 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d036e7e42..f41cb17f9 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -309,9 +309,11 @@ Zpráva neexistuje Musíte vybrat alespoň 1 příjemce Obsah zprávy musí mít alespoň 3 znaky + Všechny poštovní schránky Pouze nepřečtené Pouze s přílohami Přečtena: %s + Přečtena přes: %1$d z %2$d osob %1$d zpráva %1$d zprávy @@ -339,6 +341,7 @@ %1$d vybraných Zprávy odstraněné + Vyberte poštovní schránku Žádné informace o poznámkách Body @@ -735,7 +738,7 @@ Volbu můžete kdykoliv změnit v nastavení aplikace. Můžeme použít Vaše data k zobrazení reklam šitých pro vás nebo pomocí méně vašich dat zobrazovat nepřizpůsobené reklamy. Podrobnosti naleznete v našich Zásadách ochrany osobních údajů Přizpůsobené reklamy Nepřizpůsobené reklamy - Mám ukončené 18 let + Je mi více než 18 let Ano, přizpůsobené reklamy Ano, nepřizpůsobené reklamy Pokročilé diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7b544258a..6107fbb96 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -275,9 +275,11 @@ Nachricht nicht vorhanden Sie müssen mindestens 1 Empfänger auswählen. Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein. + All mailboxes Nur ungelesen Nur mit Anhängen Lesen: %s + Read by: %1$d of %2$d people %1$d Nachricht %1$d Nachrichten @@ -297,6 +299,7 @@ %1$d ausgewählt Nachrichten gelöscht + Choose mailbox Keine Informationen über Eintragen Punkte diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index aecc720b7..95a00a602 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -275,9 +275,11 @@ 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 @@ -297,6 +299,7 @@ %1$d selected Messages deleted + Choose mailbox No info about notes Points diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c9abbd022..f612d826d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -309,9 +309,11 @@ Wiadomość nie istnieje Musisz wybrać co najmniej 1 adresata Treść wiadomości musi zawierać co najmniej 3 znaki + Wszystkie skrzynki Tylko nieprzeczytane Tylko z załącznikami Przeczytana: %s + Przeczytana przez: %1$d z %2$d osób %1$d wiadomość %1$d wiadomości @@ -339,6 +341,7 @@ %1$d wybranych Wiadomości zostały usunięte + Wybierz skrzynkę Brak informacji o uwagach Punkty diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e1abb0293..195371fd0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -77,9 +77,9 @@ Войти Сеанс истёк Сеанс истёк, авторизуйтесь снова - Application support - Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time - Enable ads + Поддержка приложения + Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время + Включить рекламу Оценка %d семестр @@ -97,7 +97,7 @@ Ожидаемая оценка Рассчитанная средняя оценка Как работает \"Рассчитанная средняя оценка\"? - 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 + Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел Как работает \"Итоговая средняя оценка\"? Итоговая средняя оценка - это среднее арифметическое, рассчитанное из всех имеющихся на данный момент итоговых оценок в семестре.\n\nРассчет происходит следующим образом:\n1. Суммирование итоговых оценок, выставленных преподавателями\n2. Полученная сумма делится на число предметов, по которым выставлены оценки Итоговая средняя оценка @@ -297,10 +297,10 @@ Перенести в корзину Удалить навсегда Письмо успешно удалено - student - parent - guardian - employee + ученик + родитель + опекун + работник Поделиться Печать Тема @@ -309,9 +309,11 @@ Письма не существует Вы должны выбрать как минимум одного получателя Текст сообщения должен содержать как минимум 3 знака + Все почтовые ящики Только непрочитанные Только с вложениями Прочитано: %s + Прочитано: %1$d из %2$d человек %1$d сообщение %1$d сообщения @@ -339,6 +341,7 @@ %1$d выбрано Сообщение удалено + Выбрать почтовый ящик Нет записей о замечаниях и свершениях Баллы @@ -720,10 +723,10 @@ Отвечать с историей сообщений Показывать среднее арифметическое при отсутствии стоимости Поддержка - Privacy Policy - Agreements - Consent to processing of data related to ads - Show ads in app + Политика приватности + Соглашения + Согласие на обработку данных, связанных с объявлениями + Показать рекламу в приложении Посмотреть рекламу для поддержки проекта Согласие на обработку данных Для просмотра рекламы вы должны согласиться с условиями обработки данных нашей Политики конфиденциальности @@ -731,13 +734,13 @@ Политика конфиденциальности Реклама загружается Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы - 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 + Можем ли мы использовать ваши данные для показа рекламы? + Вы можете изменить свой выбор в любое время в настройках приложения. Мы можем использовать ваши данные для показа объявлений в соответствии с вашими пожеланиями или, используя меньше данных, отображать неперсональную рекламу. Пожалуйста, ознакомьтесь с нашей политикой конфиденциальности для подробностей + Персонализированная реклама + Неперсонализированная реклама + Я старше 18 лет + Да, персонализировать рекламу + Да, не персонализировать рекламу Расширенные Внешний вид и поведение Уведомления diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 1c6eae8c0..9fd06bcbe 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -309,9 +309,11 @@ Správa neexistuje Musíte vybrať aspoň 1 príjemca Obsah správy musí mať aspoň 3 znaky + Všetky poštové schránky Iba neprečítané Iba s prílohami Prečítaná: %s + Prečítaná cez: %1$d z %2$d osôb %1$d správa %1$d správy @@ -339,6 +341,7 @@ %1$d vybraných Správy odstránené + Vyberte poštovú schránku Žiadne informácie o poznámkach Body @@ -735,7 +738,7 @@ Voľbu môžete kedykoľvek zmeniť v nastavení aplikácie. Môžeme použiť vaše údaje na zobrazenie reklám šitých pre vás alebo pomocou menej vašich dát zobrazovať neprispôsobené reklamy. Podrobnosti nájdete v našich Zásadách ochrany osobných údajov Prispôsobené reklamy Neprispôsobené reklamy - Mám ukončené 18 rokov + Mám viac ako 18 rokov Áno, prispôsobené reklamy Áno, neprispôsobené reklamy Pokročilé diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2c104ecbf..2d3bf3724 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -77,8 +77,8 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову - Підтримка додатків - Вам подобається цей додаток? Підтримуйте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете будь-коли вимкнути + Підтримка додатку + Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час Увімкнути рекламу Оцінка @@ -97,7 +97,7 @@ Передбачувана оцінка Розрахована середня оцінка Як працює \"Розрахована середня оцінка\"? - Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів.Це дозволяє дізнатися приблизну кінцеву середню оцінку.Вона розраховується способом, обраним користувачем у налаштуваннях програми.Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку.Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки.Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх + Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх Як працює \"Підсумкова середня оцінка\"? Підсумкове середнє значення - це середнє арифметичне, обчислене з усіх наявних наразі підсумкових оцінок у даному семестрі. \n\nСхема обчислення складається з таких кроків:\n1. Сумування підсумкових оцінок, виставленних викладачами\n2. Ділення на кількість предметів, з яких виставлені ці оцінки Підсумкова середня оцінка @@ -220,7 +220,7 @@ Всі в серії Час початку Час завершення - Час завершення має бути більшим, ніж час початку + Час завершення має бути пізніше часу початку Підсумок відвідуваності Відсутність зі шкільних причин @@ -297,8 +297,8 @@ Перемістити до кошика Видалити назавжди Лист було успішно видалено - студент - батькові + учень + родич опікун працівник Поділитись @@ -309,9 +309,11 @@ Такого листа не існує Необхідно обрати принаймні 1 адресата Зміст листа повинен складатися принаймні з 3 знаків + Усі поштові скриньки Лише непрочитані Тільки з вкладеннями Прочитаний: %s + Прочитано: %1$d з %2$d осіб %1$d лист %1$d листи @@ -338,7 +340,8 @@ %1$d вибрано %1$d вибрано - Листи видалені + Листи видалено + Вибрати поштову скриньку Немає інформації о зауваженнях Бали @@ -722,7 +725,7 @@ Підтримка Політика конфіденційності Угоди - Згода на обробку даних, пов’язаних з оголошеннями + Згода на обробку даних, пов\'язаних з рекламою Показувати рекламу в додатку Подивіться одну рекламу для підтримки проєкту Згода в обробці даних @@ -731,9 +734,9 @@ Політика конфіденційності Реклама завантажується Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам - Чи можемо ми використовувати ваші дані для відбивання реклами? - Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для відбивання оголошень, адаптованої до вас або, використовуючи менше ваших даних, відбивати неперсоналізовані оголошення. Перегляньте нашу Політику конфіденційності для деталей - Персоналізовані оголошення + Чи можемо ми використовувати ваші дані для висвітлювання реклами? + Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для висвітлювання реклами, адаптованої до вас або, використовуючи менше ваших даних, висвітлювати неперсоналізовану рекламу. Перегляньте нашу Політику конфіденційності для подробиць + Персоналізована реклама Неперсоналізована реклама Мені більше 18 років Так, персоналізована реклама diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e365a00ca..71d1767ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,15 +26,11 @@ Student info Dashboard Notifications center - - 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> + Enter the symbol from the register page for account: <b>%1$s</b> Username Email Login, PESEL or e-mail @@ -78,8 +74,6 @@ Recover Student is already signed in Standard - - Account manager Log in @@ -88,8 +82,6 @@ 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 @@ -152,8 +144,6 @@ You received %1$d final grade You received %1$d final grades - - Lesson Room @@ -189,8 +179,6 @@ %d change %d changes - - Completed lessons Show completed lessons @@ -198,8 +186,6 @@ Topic Absence Resources - - Additional lessons Show additional lessons @@ -215,8 +201,6 @@ Start time End time End time must be greater than start time - - Attendance summary Absent for school reasons @@ -249,12 +233,8 @@ %d attendance %d attendance - - Total - - No exams this week Type @@ -271,8 +251,6 @@ %d exam %d exams - - Inbox Sent @@ -301,7 +279,7 @@ Message does not exist You need to choose at least 1 recipient The message content must be at least 3 characters - Wszystkie skrzynki + All mailboxes Only unread Only with attachments Read: %s @@ -326,8 +304,6 @@ Messages deleted Choose mailbox - - No info about notes Points @@ -343,7 +319,6 @@ You received %1$d note You received %1$d notes - %d praise @@ -357,7 +332,6 @@ You received %1$d praise You received %1$d praises - %d neutral note @@ -371,8 +345,6 @@ You received %1$d neutral note You received %1$d neutral notes - - No info about homework Mark as done @@ -393,8 +365,6 @@ %d homework %d homework - - Lucky number Today\'s lucky number is @@ -402,13 +372,9 @@ Lucky number for today Today\'s lucky number is: %s Show history - - Lucky number history No info about lucky numbers - - Mobile devices No devices @@ -418,12 +384,8 @@ Token Symbol PIN - - School and teachers - - School No info about school @@ -434,14 +396,10 @@ Name of pedagogue Show on map Call - - Teachers No info about teachers No subject - - Conferences No info about conferences @@ -459,7 +417,6 @@ Present at conference Agenda - School announcements No school announcements @@ -475,8 +432,6 @@ You have %1$d new school announcement You have %1$d new school announcements - - Add account Logout @@ -491,8 +446,6 @@ Contact Residence details Personal information - - App version Contributors @@ -516,17 +469,11 @@ Licenses Licenses of libraries used in the application Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nIdentyfikator instalacji: %4$s\nTreść zgłoszenia: - - License - - Avatar See more on GitHub - - No info about student or student family Name @@ -549,19 +496,13 @@ Female Last name Guardian - - Nick Add nick Choose avatar color - - Share logs Refresh - - Lessons (Tomorrow) @@ -580,7 +521,6 @@ 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 @@ -589,11 +529,9 @@ %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 @@ -601,7 +539,6 @@ %1$d more announcement %1$d more announcements - Exams No upcoming exams An error occurred while loading the exams @@ -609,7 +546,6 @@ %1$d more exam %1$d more exams - Conferences No upcoming conferences An error occurred while loading the conferences @@ -617,16 +553,11 @@ %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 @@ -654,16 +585,12 @@ Undo Change Add to calendar - - No lessons Choose theme Light Dark System Theme - - App Default view @@ -679,7 +606,6 @@ Grades color scheme Subjects sorting Language - Notifications Other Show notifications @@ -699,7 +625,6 @@ 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 @@ -710,12 +635,10 @@ 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 @@ -735,13 +658,11 @@ I am over 18 years old Yes, personalized ads Yes, non-personalized ads - Advanced Appearance & Behavior Notifications Synchronization Advertisements - Grades Dashboard Tiles visibility @@ -750,7 +671,6 @@ Grades Calculated average Messages - Appearance & Behavior Languages, themes, subjects sorting App notifications, fix problems @@ -761,8 +681,6 @@ Advanced App version, contributors, social portals Displaying advertisements, project support - - New grades New homework @@ -777,8 +695,6 @@ Debug Timetable change New attendance - - Black Red @@ -786,15 +702,11 @@ 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 - - No internet connection An error occurred. Check your device clock From 2d83218f618fcb927e9a6247817623ed5562e301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 16 Nov 2022 20:31:02 +0100 Subject: [PATCH 058/545] Version 1.8.0 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 99672f1f9..62a5a21fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 32 - versionCode 114 - versionName "1.7.5" + versionCode 115 + versionName "1.8.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -160,8 +160,8 @@ kapt { play { defaultToAppBundles = false track = 'production' -// releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS -// userFraction = 0.05d + releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS + userFraction = 0.25d updatePriority = 4 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:701016eda2" + implementation "io.github.wulkanowy:sdk:1.8.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 064401abd..996d5eebc 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,9 @@ -Wersja 1.7.5 +Wersja 1.8.0 -- naprawiliśmy kilka błędów w obsłudze nowego modułu wiadomości -- naprawiliśmy wyświetlanie napisu "Brak ocen", jesli uczeń nie zdobył w danym semestrze jeszcze żadnych ocen -- naprawiliśmy logowanie do aplikacji rodzicom będącym jednocześnie nauczycielami +- naprawiliśmy liczenie średniej ucznia w ocenach klasy dla wykresu "Wszystkie" +- zmieniliśmy kolejność przycisków akcji w podglądzie wiadomości +- ulepszyliśmy oznaczenie nieodczytanych wiadomości +- naprawiliśmy pokazywanie informacji o odczytanej wiadomości w wysłanych +- dodaliśmy opcję ręcznego wybierania skrzynki pocztowej Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 6c115fb915cfcc29c1a25e3f94476c473a4e1ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 18 Nov 2022 16:32:04 +0100 Subject: [PATCH 059/545] Fallback to subject name from timetable when attendance item doesn't have it (#2052) * Fallback to subject name from timetable when attendance item doesn't have it * Fix tests * Add some unit tests --- .../java/io/github/wulkanowy/data/Resource.kt | 2 +- .../wulkanowy/data/db/dao/TimetableDao.kt | 3 + .../data/mappers/AttendanceMapper.kt | 9 +- .../data/repositories/AttendanceRepository.kt | 11 +- .../data/mappers/AttendanceMapperKtTest.kt | 143 ++++++++++++++++++ .../repositories/AttendanceRepositoryTest.kt | 27 ++-- 6 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt 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 44f8a1b48..6b611e477 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -49,8 +49,8 @@ fun Resource.mapData(block: (T) -> U) = when (this) { fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach { val description = when (it) { - is Resource.Loading -> "started" is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Loading -> "started" is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" is Resource.Error -> "exception occurred: ${it.error}" } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index 5e6eec668..b4b7379f2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -13,4 +13,7 @@ interface TimetableDao : BaseDao { @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> + + @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") + fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt index 46e67fdaa..c0ed0c8c2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt @@ -3,17 +3,22 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary -fun List.mapToEntities(semester: Semester) = map { +fun List.mapToEntities(semester: Semester, lessons: List) = map { Attendance( studentId = semester.studentId, diaryId = semester.diaryId, date = it.date, timeId = it.timeId, number = it.number, - subject = it.subject, + subject = it.subject.ifBlank { + lessons.find { lesson -> + lesson.date == it.date && lesson.number == it.number + }?.subject.orEmpty() + }, name = it.name, presence = it.presence, absence = it.absence, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 9aa6562a6..fd5d8bd16 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -9,8 +10,10 @@ import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.utils.* +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withContext import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime @@ -20,6 +23,7 @@ import javax.inject.Singleton @Singleton class AttendanceRepository @Inject constructor( private val attendanceDb: AttendanceDao, + private val timetableDb: TimetableDao, private val sdk: Sdk, private val refreshHelper: AutoRefreshHelper, ) { @@ -48,10 +52,15 @@ class AttendanceRepository @Inject constructor( attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) }, fetch = { + val lessons = withContext(Dispatchers.IO) { + timetableDb.load( + semester.diaryId, semester.studentId, start.monday, end.sunday + ) + } sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .getAttendance(start.monday, end.sunday, semester.semesterId) - .mapToEntities(semester) + .mapToEntities(semester, lessons) }, saveFetchResult = { old, new -> attendanceDb.deleteAll(old uniqueSubtract new) diff --git a/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt new file mode 100644 index 000000000..a35e5d303 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.sdk.pojo.Attendance +import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuse +import org.junit.Test +import java.time.Instant +import java.time.LocalDate +import kotlin.test.assertEquals + +class AttendanceMapperTest { + + @Test + fun `map attendance when fallback is not necessary`() { + val attendance = listOf( + getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"), + getSdkAttendance(2, LocalDate.of(2022, 11, 17), "Oryginalna 2"), + ) + val lessons = listOf( + getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"), + getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"), + ) + + val result = attendance.mapToEntities(getEntitySemester(), lessons) + assertEquals("Oryginalna 1", result[0].subject) + assertEquals("Oryginalna 2", result[1].subject) + } + + @Test + fun `map attendance when fallback is not always necessary`() { + val attendance = listOf( + getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"), + getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""), + ) + val lessons = listOf( + getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"), + getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"), + ) + + val result = attendance.mapToEntities(getEntitySemester(), lessons) + assertEquals("Oryginalna 1", result[0].subject) + assertEquals("Druga", result[1].subject) + } + + @Test + fun `map attendance when fallback is sometimes empty`() { + val attendance = listOf( + getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"), + getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""), + ) + val lessons = listOf( + getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"), + ) + + val result = attendance.mapToEntities(getEntitySemester(), lessons) + assertEquals("Oryginalna 1", result[0].subject) + assertEquals("", result[1].subject) + } + + @Test + fun `map attendance when fallback is empty`() { + val attendance = listOf( + getSdkAttendance(1, LocalDate.of(2022, 11, 17), ""), + getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""), + ) + val lessons = listOf( + getEntityTimetable(1, LocalDate.of(2022, 11, 18), "Pierwsza"), + getEntityTimetable(2, LocalDate.of(2022, 10, 17), "Druga"), + ) + + val result = attendance.mapToEntities(getEntitySemester(), lessons) + assertEquals("", result[0].subject) + assertEquals("", result[1].subject) + } + + @Test + fun `map attendance with all subject fallback`() { + val attendance = listOf( + getSdkAttendance(1, LocalDate.of(2022, 11, 17)), + getSdkAttendance(2, LocalDate.of(2022, 11, 17)), + ) + val lessons = listOf( + getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"), + getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"), + ) + + val result = attendance.mapToEntities(getEntitySemester(), lessons) + assertEquals("Pierwsza", result[0].subject) + assertEquals("Druga", result[1].subject) + } + + private fun getSdkAttendance(number: Int, date: LocalDate, subject: String = "") = Attendance( + number = number, + name = "ABSENCE", + subject = subject, + date = date, + timeId = 1, + categoryId = 1, + deleted = false, + excuseStatus = SentExcuse.Status.WAITING, + excusable = false, + absence = false, + excused = false, + exemption = false, + lateness = false, + presence = false, + ) + + private fun getEntityTimetable(number: Int, date: LocalDate, subject: String = "") = Timetable( + number = number, + start = Instant.now(), + end = Instant.now(), + date = date, + subject = subject, + subjectOld = "", + group = "", + room = "", + roomOld = "", + teacher = "", + teacherOld = "", + info = "", + changes = false, + canceled = false, + studentId = 0, + diaryId = 0, + isStudentPlan = false, + ) + + private fun getEntitySemester() = Semester( + studentId = 0, + diaryId = 0, + kindergartenDiaryId = 0, + diaryName = "", + schoolYear = 0, + semesterId = 0, + semesterName = 0, + start = LocalDate.now(), + end = LocalDate.now(), + classId = 0, + unitId = 0 + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index 7d22f7265..896491ef0 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.toFirstResult @@ -29,6 +30,9 @@ class AttendanceRepositoryTest { @MockK private lateinit var attendanceDb: AttendanceDao + @MockK + private lateinit var timetableDb: TimetableDao + @MockK(relaxUnitFun = true) private lateinit var refreshHelper: AutoRefreshHelper @@ -51,8 +55,9 @@ class AttendanceRepositoryTest { fun setUp() { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false + coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList() - attendanceRepository = AttendanceRepository(attendanceDb, sdk, refreshHelper) + attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper) } @Test @@ -60,8 +65,8 @@ class AttendanceRepositoryTest { // prepare coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( - flowOf(remoteList.mapToEntities(semester)), - flowOf(remoteList.mapToEntities(semester)) + flowOf(remoteList.mapToEntities(semester, emptyList())), + flowOf(remoteList.mapToEntities(semester, emptyList())) ) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.deleteAll(any()) } just Runs @@ -83,9 +88,9 @@ class AttendanceRepositoryTest { // prepare coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( - flowOf(remoteList.dropLast(1).mapToEntities(semester)), - flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result - flowOf(remoteList.mapToEntities(semester)) + flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), + flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result + flowOf(remoteList.mapToEntities(semester, emptyList())) ) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.deleteAll(any()) } just Runs @@ -100,7 +105,7 @@ class AttendanceRepositoryTest { coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] }) } coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } @@ -111,9 +116,9 @@ class AttendanceRepositoryTest { // prepare coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1) coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( - flowOf(remoteList.mapToEntities(semester)), - flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result - flowOf(remoteList.dropLast(1).mapToEntities(semester)) + flowOf(remoteList.mapToEntities(semester, emptyList())), + flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result + flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())) ) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.deleteAll(any()) } just Runs @@ -129,7 +134,7 @@ class AttendanceRepositoryTest { coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { attendanceDb.deleteAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] }) } } From b16036774460ab500303ab7cb8093ee1584aa8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 19 Nov 2022 08:52:35 +0100 Subject: [PATCH 060/545] Add support for reversed student name mailbox matching (#2051) * Add support for reversed student name mailbox matching * Add additional log stmt to mailbox matching in all mailbox load * Revert changes in mapper --- .../github/wulkanowy/data/mappers/MessageMapper.kt | 2 +- .../domain/messages/GetMailboxByStudentUseCase.kt | 10 ++++++++++ .../domain/GetMailboxByStudentUseCaseTest.kt | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 8825c5744..87111dd4d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient -fun List.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List) = map { +fun List.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List): List = map { Message( messageGlobalKey = it.globalKey, mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt index d96794e51..a696d9b2f 100644 --- a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt +++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt @@ -21,6 +21,8 @@ class GetMailboxByStudentUseCase @Inject constructor( it.studentName.normalizeStudentName() == normalizedStudentName } ?: singleOrNull { it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() + } ?: singleOrNull { + it.studentName.getReversedName() == normalizedStudentName } ?: singleOrNull { it.studentName.getUnauthorizedVersion() == normalizedStudentName } @@ -43,6 +45,14 @@ class GetMailboxByStudentUseCase @Inject constructor( return endParts.joinToString(" ") } + private fun String.getReversedName(): String { + val parts = normalizeStudentName().split(" ") + + return parts + .asReversed() + .joinToString(" ") + } + private fun String.getUnauthorizedVersion(): String { return normalizeStudentName().split(" ") .joinToString(" ") { 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 96a84a5a6..029800266 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -64,6 +64,18 @@ class GetMailboxByStudentUseCaseTest { assertEquals(expectedMailbox, selectedMailbox) } + @Test + fun `get mailbox for user with reversed name`() = runTest { + val student = getStudentEntity( + userName = "Kowalski Jan", + studentName = "Jan Kowalski", + ) + val expectedMailbox = getMailboxEntity("Kowalski Jan") + coEvery { mailboxDao.loadAll(any()) } returns listOf(expectedMailbox) + + assertEquals(expectedMailbox, systemUnderTest(student)) + } + @Test fun `get mailbox for unique non-authorized student`() = runTest { val student = getStudentEntity( From 650cbd5a10f8d6e27242f7306daa546cab7ed4ad Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Sat, 19 Nov 2022 14:11:26 +0100 Subject: [PATCH 061/545] Add info about optional ads in README (#2054) --- README.cs.md | 2 +- README.de.md | 4 ++-- README.en.md | 2 +- README.md | 2 +- README.sk.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.cs.md b/README.cs.md index 2b0dc12ea..d3d4e2557 100644 --- a/README.cs.md +++ b/README.cs.md @@ -34,7 +34,7 @@ Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče * podpora více účtů s možností přejmenování žáků * tmavý a černý (AMOLED) motiv * offline režim -* žádné reklamy +* volitelné reklamy na podporu projektu ## Stáhnout diff --git a/README.de.md b/README.de.md index 6df10ecd0..853abd13e 100644 --- a/README.de.md +++ b/README.de.md @@ -21,7 +21,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre * Prozentsatz der Anwesenheit * Prüfungen * Stundenplan - * Unterricht abgeschlossen + * abgeschlossene Unterrichtsstunden * Nachrichten * Hausaufgaben * Anmerkungen @@ -34,7 +34,7 @@ Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre * Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern * dunkles und schwarzes (AMOLED) Thema * Offline-Modus -* keine Werbung +* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen ## Herunterladen diff --git a/README.en.md b/README.en.md index 417b74de0..7877bf377 100644 --- a/README.en.md +++ b/README.en.md @@ -34,7 +34,7 @@ Unofficial android VULCAN UONET+ register client for both students and their par * support for multiple accounts with the ability to rename students * dark and black (AMOLED) theme * offline mode -* no ads +* optional ads which allow to support the project ## Download diff --git a/README.md b/README.md index 75b6cfca2..09480e7d7 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica * obsługa wielu kont wraz z możliwością zmiany nazwy ucznia * ciemny i czarny (AMOLED) motyw * tryb offline -* brak reklam +* opcjonalne reklamy umożliwiające wsparcie projektu ## Pobierz diff --git a/README.sk.md b/README.sk.md index 240f8835c..64786556e 100644 --- a/README.sk.md +++ b/README.sk.md @@ -34,7 +34,7 @@ Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov * podpora viacerých účtov s možnosťou premenovania žiakov * tmavý a čierny (AMOLED) motív * offline režim -* žiadne reklamy +* voliteľné reklamy na podporu projektu ## Stiahnuť From fdce2cf477cc98d4035431e1419e046d1db6d2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 19 Nov 2022 22:50:51 +0100 Subject: [PATCH 062/545] Improve error handling in horizontal tile on dashboard (#2053) --- .../wulkanowy/data/mappers/MessageMapper.kt | 10 +- .../data/repositories/MessageRepository.kt | 14 +- .../ui/modules/dashboard/DashboardItem.kt | 19 ++- .../modules/dashboard/DashboardPresenter.kt | 82 ++++++----- .../dashboard/adapters/DashboardAdapter.kt | 136 ++++++++++-------- .../wulkanowy/utils/ContextExtension.kt | 7 + app/src/main/res/drawable/ic_error_filled.xml | 9 ++ .../item_dashboard_horizontal_group.xml | 53 ++++++- 8 files changed, 228 insertions(+), 102 deletions(-) create mode 100644 app/src/main/res/drawable/ic_error_filled.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 87111dd4d..120eb183a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -6,12 +6,18 @@ import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient -fun List.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List): List = map { +fun List.mapToEntities( + student: Student, + mailbox: Mailbox?, + allMailboxes: List +): List = map { Message( messageGlobalKey = it.globalKey, mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> box.fullName == it.mailbox - }?.globalKey!!, + }?.globalKey.let { mailboxKey -> + requireNotNull(mailboxKey) { "Can't find ${it.mailbox} in $allMailboxes" } + }, email = student.email, messageId = it.id, correspondents = it.correspondents, 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 f8be4296d..f95b8dbec 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,7 +3,7 @@ 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.Resource +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao @@ -14,9 +14,7 @@ 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.pojos.MessageDraft -import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder @@ -194,7 +192,9 @@ class MessageRepository @Inject constructor( it.isEmpty() || isExpired || forceRefresh }, query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, - fetch = { sdk.init(student).getMailboxes().mapToEntities(student) }, + fetch = { + sdk.init(student).getMailboxes().mapToEntities(student) + }, saveFetchResult = { old, new -> mailboxDao.deleteAll(old uniqueSubtract new) mailboxDao.insertAll(new uniqueSubtract old) @@ -207,7 +207,11 @@ class MessageRepository @Inject constructor( val mailbox = getMailboxByStudentUseCase(student) return if (mailbox == null) { - getMailboxes(student, forceRefresh = true).toFirstResult() + getMailboxes(student, forceRefresh = true) + .onResourceError { throw it } + .onResourceSuccess { Timber.i("Found ${it.size} new mailboxes") } + .waitForResult() + getMailboxByStudentUseCase(student) } else mailbox } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt index e220ae236..d019dea68 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardItem.kt @@ -33,18 +33,27 @@ sealed class DashboardItem(val type: Type) { } data class HorizontalGroup( - val unreadMessagesCount: Int? = null, - val attendancePercentage: Double? = null, - val luckyNumber: Int? = null, + val unreadMessagesCount: Cell? = null, + val attendancePercentage: Cell? = null, + val luckyNumber: Cell? = null, override val error: Throwable? = null, override val isLoading: Boolean = false ) : DashboardItem(Type.HORIZONTAL_GROUP) { + data class Cell( + val data: T?, + val error: Boolean, + val isLoading: Boolean, + ) { + val isHidden: Boolean + get() = data == null && !error && !isLoading + } + override val isDataLoaded - get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null + get() = unreadMessagesCount?.isLoading == false || attendancePercentage?.isLoading == false || luckyNumber?.isLoading == false val isFullDataLoaded - get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1 + get() = luckyNumber?.isLoading != true && attendancePercentage?.isLoading != true && unreadMessagesCount?.isLoading != true } data class Grades( 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 cb92b0043..22b0d267e 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 @@ -226,50 +226,71 @@ class DashboardPresenter @Inject constructor( private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { flow { - val semester = semesterRepository.getCurrentSemester(student) - val mailbox = messageRepository.getMailboxByStudent(student) val selectedTiles = preferencesRepository.selectedDashboardTiles - val flowSuccess = flowOf(Resource.Success(null)) + val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) .mapResourceData { it ?: LuckyNumber(0, LocalDate.now(), 0) } + .onResourceError { errorHandler.dispatch(it) } .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess - val messageFLow = messageRepository.getMessages( - student = student, - mailbox = mailbox, - folder = MessageFolder.RECEIVED, - forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess + val messageFLow = flatResourceFlow { + val mailbox = messageRepository.getMailboxByStudent(student) - val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary( - student = student, - semester = semester, - subjectId = -1, - forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess + messageRepository.getMessages( + student = student, + mailbox = mailbox, + folder = MessageFolder.RECEIVED, + forceRefresh = forceRefresh + ) + } + .onResourceError { errorHandler.dispatch(it) } + .takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess + + val attendanceFlow = flatResourceFlow { + val semester = semesterRepository.getCurrentSemester(student) + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = -1, + forceRefresh = forceRefresh + ) + } + .onResourceError { errorHandler.dispatch(it) } + .takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess emitAll( combine( - luckyNumberFlow, - messageFLow, - attendanceFlow + flow = luckyNumberFlow, + flow2 = messageFLow, + flow3 = attendanceFlow, ) { luckyNumberResource, messageResource, attendanceResource -> val resList = listOf(luckyNumberResource, messageResource, attendanceResource) - resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it } - val isLoading = resList.any { it is Resource.Loading } - - val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber - val messageCount = messageResource.dataOrNull?.count { it.unread } - val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage() DashboardItem.HorizontalGroup( - isLoading = isLoading, - attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage, - unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount, - luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber + isLoading = resList.any { it is Resource.Loading }, + error = resList.map { it.errorOrNull }.let { errors -> + if (errors.all { it != null }) { + errors.firstOrNull() + } else null + }, + attendancePercentage = DashboardItem.HorizontalGroup.Cell( + data = attendanceResource.dataOrNull?.calculatePercentage(), + error = attendanceResource.errorOrNull != null, + isLoading = attendanceResource is Resource.Loading, + ), + unreadMessagesCount = DashboardItem.HorizontalGroup.Cell( + data = messageResource.dataOrNull?.count { it.unread }, + error = messageResource.errorOrNull != null, + isLoading = messageResource is Resource.Loading, + ), + luckyNumber = DashboardItem.HorizontalGroup.Cell( + data = luckyNumberResource.dataOrNull?.luckyNumber, + error = luckyNumberResource.errorOrNull != null, + isLoading = luckyNumberResource is Resource.Loading, + ) ) }) } @@ -280,11 +301,8 @@ class DashboardPresenter @Inject constructor( if (it.isLoading) { Timber.i("Loading horizontal group data started") - - if (it.isFullDataLoaded) { - firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP - } } else { + firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP Timber.i("Loading horizontal group result: Success") } } 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 a3c423a8b..2c06e45fd 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 @@ -171,81 +171,105 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter { + updateMarginsRelative( + end = if (isAttendanceHidden && isMessagesHidden && !isLuckyNumberHidden) { + 0 + } else context.dpToPx(8f).toInt() + ) + } + } + } + + private fun ItemDashboardHorizontalGroupBinding.bindMessages( + item: DashboardItem.HorizontalGroup, + isWideErrorShow: Boolean + ) { + dashboardHorizontalGroupItemMessageError.isVisible = item.unreadMessagesCount?.error == true + with(dashboardHorizontalGroupItemMessageValue) { + isVisible = item.unreadMessagesCount?.error != true + text = item.unreadMessagesCount?.data.toString() + } + with(dashboardHorizontalGroupItemMessageContainer) { + isVisible = item.unreadMessagesCount?.isHidden == false && !isWideErrorShow + setOnClickListener { onMessageTileClickListener() } + } + } + + private fun ItemDashboardHorizontalGroupBinding.bindAttendance( + item: DashboardItem.HorizontalGroup, + isWideErrorShow: Boolean + ) { + val attendancePercentage = item.attendancePercentage?.data val attendanceColor = when { attendancePercentage == null || attendancePercentage == .0 -> { - context.getThemeAttrColor(R.attr.colorOnSurface) + root.context.getThemeAttrColor(R.attr.colorOnSurface) } attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> { - context.getThemeAttrColor(R.attr.colorPrimary) + root.context.getThemeAttrColor(R.attr.colorPrimary) } attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> { - context.getThemeAttrColor(R.attr.colorTimetableChange) + root.context.getThemeAttrColor(R.attr.colorTimetableChange) } - else -> context.getThemeAttrColor(R.attr.colorOnSurface) + else -> root.context.getThemeAttrColor(R.attr.colorOnSurface) } val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) { - context.getString(R.string.dashboard_horizontal_group_no_data) + root.context.getString(R.string.dashboard_horizontal_group_no_data) } else { "%.2f%%".format(attendancePercentage) } - with(binding.dashboardHorizontalGroupItemAttendanceValue) { + dashboardHorizontalGroupItemAttendanceError.isVisible = + item.attendancePercentage?.error == true + with(dashboardHorizontalGroupItemAttendanceValue) { + isVisible = item.attendancePercentage?.error != true text = attendanceString setTextColor(attendanceColor) } - - with(binding) { - dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString() - dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) { - context.getString(R.string.dashboard_horizontal_group_no_data) - } else luckyNumber?.toString() - - dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoadingVisible - dashboardHorizontalGroupItemInfoProgress.isVisible = isLoadingVisible - dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null - - with(dashboardHorizontalGroupItemLuckyContainer) { - isVisible = luckyNumber != null && luckyNumber != -1 && !isLoadingVisible - setOnClickListener { onLuckyNumberTileClickListener() } - - updateLayoutParams { - updateMarginsRelative( - end = if (attendancePercentage == null && unreadMessagesCount == null && luckyNumber != null) { - 0 - } else { - context.dpToPx(8f).toInt() - } - ) + with(dashboardHorizontalGroupItemAttendanceContainer) { + isVisible = item.attendancePercentage?.isHidden == false && !isWideErrorShow + setOnClickListener { onAttendanceTileClickListener() } + updateLayoutParams { + matchConstraintPercentWidth = when { + item.luckyNumber?.isHidden == true && item.unreadMessagesCount?.isHidden == true -> 1.0f + item.luckyNumber?.isHidden == true || item.unreadMessagesCount?.isHidden == true -> 0.5f + else -> 0.4f } } - - with(dashboardHorizontalGroupItemAttendanceContainer) { - isVisible = - attendancePercentage != null && attendancePercentage != -1.0 && !isLoadingVisible - updateLayoutParams { - matchConstraintPercentWidth = when { - luckyNumber == null && unreadMessagesCount == null -> 1.0f - luckyNumber == null || unreadMessagesCount == null -> 0.5f - else -> 0.4f - } - } - setOnClickListener { onAttendanceTileClickListener() } - } - - with(dashboardHorizontalGroupItemMessageContainer) { - isVisible = - unreadMessagesCount != null && unreadMessagesCount != -1 && !isLoadingVisible - setOnClickListener { onMessageTileClickListener() } - } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index dd91d36d4..cc4c5aaa4 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -2,9 +2,11 @@ package io.github.wulkanowy.utils import android.annotation.SuppressLint import android.content.Context +import android.content.res.ColorStateList import android.graphics.* import android.text.TextPaint import android.util.DisplayMetrics.DENSITY_DEFAULT +import android.widget.ImageView import androidx.annotation.* import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils @@ -12,6 +14,7 @@ import androidx.core.graphics.applyCanvas import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.toBitmap +import androidx.core.widget.ImageViewCompat @ColorInt @@ -85,3 +88,7 @@ fun Context.createNameInitialsDrawable( return RoundedBitmapDrawableFactory.create(this.resources, bitmap) .apply { isCircular = true } } + +fun ImageView.setTint(@ColorInt color: Int) { + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color)) +} diff --git a/app/src/main/res/drawable/ic_error_filled.xml b/app/src/main/res/drawable/ic_error_filled.xml new file mode 100644 index 000000000..61b575dc6 --- /dev/null +++ b/app/src/main/res/drawable/ic_error_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml index 1d43d5115..0c59d1ebf 100644 --- a/app/src/main/res/layout/item_dashboard_horizontal_group.xml +++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml @@ -37,9 +37,25 @@ app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginEnd="16dp" app:tint="?colorOnSurface" tools:ignore="ContentDescription" /> + + + + + tools:text="16" + tools:visibility="visible" /> @@ -145,9 +178,25 @@ app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginEnd="16dp" app:tint="?colorOnSurface" tools:ignore="ContentDescription" /> + + - \ No newline at end of file + From 9c60ce688b48226e7ffe44f5029a89de8744f7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 20 Nov 2022 00:05:34 +0100 Subject: [PATCH 063/545] Version 1.8.1 --- 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 62a5a21fb..f6a82ad20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 32 - versionCode 115 - versionName "1.8.0" + versionCode 116 + versionName "1.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,7 +161,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS - userFraction = 0.25d + userFraction = 0.10d updatePriority = 4 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.8.0" + implementation "io.github.wulkanowy:sdk:1.8.1" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 996d5eebc..7b2fda861 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 1.8.0 +Wersja 1.8.1 - naprawiliśmy liczenie średniej ucznia w ocenach klasy dla wykresu "Wszystkie" - zmieniliśmy kolejność przycisków akcji w podglądzie wiadomości From 49e68f5c8bdc47d276268bbd4100d47be1490112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:50:47 +0000 Subject: [PATCH 064/545] Bump agconnect-crash from 1.7.3.300 to 1.7.3.302 (#2060) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f6a82ad20..a7c09c568 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:21.3.0' hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 890d60811b9e67dd905feff4366724474dc7065c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:51:07 +0000 Subject: [PATCH 065/545] Bump agcp from 1.7.3.301 to 1.7.3.302 (#2059) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e8e1052b6..d09063fb6 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.huawei.agconnect:agcp:1.7.3.301' + classpath 'com.huawei.agconnect:agcp:1.7.3.302' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" From 85ce23845ff9498f11d053dea3babbcc17711ef6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:51:23 +0000 Subject: [PATCH 066/545] Bump firebase-bom from 31.0.3 to 31.1.0 (#2057) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a7c09c568..de3b49134 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:31.0.3') + playImplementation platform('com.google.firebase:firebase-bom:31.1.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From ae39bd94e5815b6807b60b8cbce366595b88dec3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Nov 2022 20:42:38 +0000 Subject: [PATCH 067/545] Bump hilt_version from 2.44.1 to 2.44.2 (#2058) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d09063fb6..487e5f6e3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.7.21' about_libraries = '10.5.1' - hilt_version = "2.44.1" + hilt_version = "2.44.2" } repositories { mavenCentral() From 9dc12204961c1a90df941f39136a4ba7e311ce60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 18:36:14 +0000 Subject: [PATCH 068/545] Bump about_libraries from 10.5.1 to 10.5.2 (#2066) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 487e5f6e3..b219c8318 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.7.21' - about_libraries = '10.5.1' + about_libraries = '10.5.2' hilt_version = "2.44.2" } repositories { From 8f50ee82b3275d92fd45af6948e25287b02156d0 Mon Sep 17 00:00:00 2001 From: Patryk <43276401+Zaptyp@users.noreply.github.com> Date: Mon, 28 Nov 2022 19:50:14 +0100 Subject: [PATCH 069/545] Change fakelog to https (#2063) --- app/src/main/res/values/api_hosts.xml | 2 +- app/src/main/res/xml/network_security_config.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 8413d68e4..e7373e114 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -40,7 +40,7 @@ https://vulcan.net.pl/?login https://vulcan.net.pl/?login https://vulcan.net.pl/?login - http://fakelog.cf/?email + https://fakelog.cf/?email Default diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 84ff05a04..17fac4d1e 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,5 @@ - From 302d723cfbba95279142dd04197766f863b61e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 1 Dec 2022 18:14:28 +0100 Subject: [PATCH 070/545] Suppress menu deprecations (#2031) --- .../github/wulkanowy/ui/modules/account/AccountFragment.kt | 1 + .../modules/account/accountdetails/AccountDetailsFragment.kt | 1 + .../wulkanowy/ui/modules/attendance/AttendanceFragment.kt | 1 + .../wulkanowy/ui/modules/dashboard/DashboardFragment.kt | 1 + .../ui/modules/debug/logviewer/LogViewerFragment.kt | 5 ++--- .../io/github/wulkanowy/ui/modules/grade/GradeFragment.kt | 1 + .../ui/modules/grade/details/GradeDetailsFragment.kt | 5 ++--- .../ui/modules/message/preview/MessagePreviewFragment.kt | 1 + .../wulkanowy/ui/modules/message/tab/MessageTabFragment.kt | 2 ++ .../wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt | 1 + .../wulkanowy/ui/modules/timetable/TimetableFragment.kt | 1 + 11 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt index 051c93c95..f115372a5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountFragment.kt @@ -34,6 +34,7 @@ class AccountFragment : BaseFragment(R.layout.fragment_a override val titleStringId = R.string.account_title + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt index c3137ec58..c6fe8a69b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -43,6 +43,7 @@ class AccountDetailsFragment : } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 6354b5e04..21f30b046 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -84,6 +84,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index de0b4a6c9..cd66d6c2f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -61,6 +61,7 @@ class DashboardFragment : BaseFragment(R.layout.fragme fun newInstance() = DashboardFragment() } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt index 1e11c874b..929e72645 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerFragment.kt @@ -1,9 +1,7 @@ package io.github.wulkanowy.ui.modules.debug.logviewer import android.content.Intent -import android.content.Intent.EXTRA_EMAIL -import android.content.Intent.EXTRA_STREAM -import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION +import android.content.Intent.* import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -36,6 +34,7 @@ class LogViewerFragment : BaseFragment(R.layout.fragme fun newInstance() = LogViewerFragment() } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index 0a8561eec..15df47a19 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -51,6 +51,7 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade override val currentPageIndex get() = binding.gradeViewPager.currentItem + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt index 81f3226ad..23d767a6f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsFragment.kt @@ -5,9 +5,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -42,6 +40,7 @@ class GradeDetailsFragment : override val isViewEmpty get() = gradeDetailsAdapter.itemCount == 0 + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) 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 2a5523f4d..8c6b0402b 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 @@ -73,6 +73,7 @@ class MessagePreviewFragment : } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) 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 5d608ad3b..eddb43243 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 @@ -86,6 +86,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -135,6 +136,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag } } + @Suppress("DEPRECATION") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.action_menu_message_tab, menu) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt index 361a59440..6ff7a39b7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt @@ -65,6 +65,7 @@ class StudentInfoFragment : } } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index fdd4aface..6fd126326 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -51,6 +51,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) From 429fdfa4a0380bb520e116e0112c01c524f5de1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 1 Dec 2022 19:02:25 +0100 Subject: [PATCH 071/545] Update project to Android SDK 33 (#2011) --- app/build.gradle | 14 ++-- .../res/mipmap-anydpi-v26/ic_launcher.xml | 3 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 -- .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4369 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2798 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6193 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 9606 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 13447 -> 0 bytes app/src/main/AndroidManifest.xml | 8 +- .../github/wulkanowy/ui/base/ErrorDialog.kt | 2 +- .../github/wulkanowy/ui/base/ThemeManager.kt | 18 ++++- .../accountdetails/AccountDetailsFragment.kt | 11 +-- .../account/accountedit/AccountEditDialog.kt | 13 ++-- .../accountquick/AccountQuickDialog.kt | 10 +-- .../ui/modules/attendance/AttendanceDialog.kt | 8 +- .../ui/modules/conference/ConferenceDialog.kt | 10 +-- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 8 +- .../grade/details/GradeDetailsDialog.kt | 20 +++-- .../statistics/GradeStatisticsFragment.kt | 5 +- .../homework/details/HomeworkDetailsDialog.kt | 8 +- .../ui/modules/login/LoginActivity.kt | 27 ++++++- .../login/recover/LoginRecoverFragment.kt | 2 +- .../LoginStudentSelectFragment.kt | 9 +-- .../LoginStudentSelectPresenter.kt | 2 +- .../studentselect/LoginStudentSelectView.kt | 2 +- .../login/symbol/LoginSymbolFragment.kt | 8 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 13 +++- .../ui/modules/main/MainPresenter.kt | 7 +- .../mailboxchooser/MailboxChooserDialog.kt | 4 +- .../message/preview/MessagePreviewFragment.kt | 12 +-- .../message/send/SendMessageActivity.kt | 9 ++- .../modules/message/tab/MessageTabFragment.kt | 12 ++- .../wulkanowy/ui/modules/note/NoteDialog.kt | 10 +-- .../notifications/NotificationsFragment.kt | 64 ++++++++++++++++ .../SchoolAnnouncementDialog.kt | 10 +-- .../notifications/NotificationsFragment.kt | 63 ++++++++++------ .../notifications/NotificationsPresenter.kt | 32 ++++++-- .../notifications/NotificationsView.kt | 10 ++- .../studentinfo/StudentInfoFragment.kt | 22 +++--- .../ui/modules/timetable/TimetableDialog.kt | 14 ++-- .../ui/modules/timetable/TimetableFragment.kt | 5 +- .../completed/CompletedLessonDialog.kt | 10 +-- .../github/wulkanowy/utils/BundleExtension.kt | 32 ++++++++ .../io/github/wulkanowy/utils/IntentUtils.kt | 21 ++++++ .../ic_launcher_foreground_dev_mono.xml | 20 +++++ .../drawable/ic_launcher_foreground_mono.xml | 12 +++ .../res/layout/fragment_notifications.xml | 69 ++++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 3 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 -- .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4025 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2569 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 5740 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 8796 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 12531 -> 0 bytes app/src/main/res/values/strings.xml | 5 ++ .../main/res/xml/data_extraction_rules.xml | 17 +++++ .../LoginStudentSelectPresenterTest.kt | 4 +- 57 files changed, 496 insertions(+), 182 deletions(-) delete mode 100644 app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/src/debug/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/debug/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt create mode 100644 app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground_mono.xml create mode 100644 app/src/main/res/layout/fragment_notifications.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/xml/data_extraction_rules.xml diff --git a/app/build.gradle b/app/build.gradle index de3b49134..cf7223bae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,13 +16,13 @@ apply from: 'hooks.gradle' android { namespace 'io.github.wulkanowy' - compileSdkVersion 32 + compileSdkVersion 33 defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 116 versionName "1.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -193,9 +193,9 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" - implementation "androidx.core:core-ktx:1.8.0" + implementation "androidx.core:core-ktx:1.9.0" implementation 'androidx.core:core-splashscreen:1.0.0' - implementation "androidx.activity:activity-ktx:1.5.1" + implementation "androidx.activity:activity-ktx:1.6.1" implementation "androidx.appcompat:appcompat:1.5.1" implementation "androidx.fragment:fragment-ktx:1.5.4" implementation "androidx.annotation:annotation:1.5.0" @@ -271,9 +271,9 @@ dependencies { testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" - androidTestImplementation "androidx.test:core:1.4.0" - androidTestImplementation "androidx.test:runner:1.4.0" - androidTestImplementation "androidx.test.ext:junit:1.1.3" + androidTestImplementation "androidx.test:core:1.5.0" + androidTestImplementation "androidx.test:runner:1.5.1" + androidTestImplementation "androidx.test.ext:junit:1.1.4" androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" } diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml index 7dbec2cb9..b7b756b9e 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ - \ No newline at end of file + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 7dbec2cb9..000000000 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 81e723eccf63eb93c41506650615abaf157a8f93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4369 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84mJh`hS0a0-5D4JZg{#lhE&{o8p9b7{9LRr zYR%Oyg_YciywPQgG_vk4So=Hp^|j{~cC}&FR$?o|0++1MdmOwv`FZA6yXf*OVPcG~ zf-a1Vg^a44>ogXXHa}wOSfujr{hZm=hkZRFoP@UO8>jz!vhM%Scjecr^DX9>|4@|i zB(_vF{yd-vYoFD7sx)Kg`h zLfn=ekBnl{&l%qD95pHCYZ`wOw~9oon#|w?ww;FEd=2*Ie+@ji~Um z*uK$nZy@`b4Q`5)E1AA7zIi|*uGE`b&iX!s#vJBzM+@rA{y$-3x$!bWd2%iDfjt46 z)N*|jd>D_;5q=ibTWn+&KD&uInVms(;?WsW(Tz!7Gb}HNzvei1ahZalI+K+5120kS zm!6S__D4(zlw#AEle{iOYb&Qm&*SFF{EkxE_l2&RoKTy&=GPn_vn#94%|1Hkqt-Ox zy6A&}66XTdlr4o$-d}r276-E<^i(l^Vi@1-o-6k-{Q zy#>3UiW!=#wQIb&dV1c|3HL1vcpRCQ*IEZ^m8QJDq0{i`Wk8;HJI_<*z;7=cr`+BB z`dCvV^U19nlRX{(|8r5CZhpEj-D|0*%r5b(I+vNe+HMKSg#0^qY3Y)Z^Kze_{3r|( zllGpcq8|Ks^>Y7Cx5bK~M~^N^cy#31oaJm^l#aVDVojSV75pZ4%4u7MInRCa5`7c& z>VIkO{CG8diH-H_6{}ZkD!iI}n;~M`w?i|hswZx+)ShbBFUVDU!^)gf!Qv-l_Gb38 z&yRh1xx`4ne?|Ph903WpRbE~maTOLP87~{(zS7|OQt6^2-`$rvvobxGGKZARP4<0e zb7$Aqiwh)9b-VX_2>np~pq@5Y`b#x0-|QnCEY4E7Sv{9`y?8%A>FtV85h16zJNt6Q zEcCUtPrg0kB6NSVdZ%!=r`ZO@`)5>)e0A(@YP6rd@Mh2c`UPE~n$brk4^OS!fhe zzuoUWY`A62JbX>fz1$hhTs3;uTt6dGHTTuEx5^IN&fJ=~y5N+^qLnK%O$=UzuRrnm zQc%!`%l;~2f7=)>awhnhAG|Z8qby3N?o0Fb?cFDzOmuwL{^M6& z?j+YOZ?`t^KF$pd65)|>c-Qpk>i758jaV5@NAHVyUBl=yrP<;qn}gw$3X|<_O-Ei- z`KVZ(GQ9RBJ@2wh=TX^%a`HEn46ig^*{)wXYocS80{bdGef4^WZ#6F-_s{0>sC{-V z!uYgEx3|)rySq0TUhIy0nN+yp65G!H#6=N1awILq1s(amzfKoScxCaUH?Vq8Q;++2f3jds4Jb@46tJq>n7M6GOz+p96Z*xq)oOsmk6 zoeD=U+pT0gzINY&ug=~UZRU!ZY+G%Vr@Gx*bibH$O5L&m0alTv=`&VK%lPQ6jrCsl z_Ll2AMuQoz7x)LQiO&}hL=eZXH=Zkda{VKG0TiQ3vv+G)AW$CHu zC%x8)9$d#G@8@-(CeBf<$Mb&cRPEBqb{~(5TTM1nJFmdo!Cs)tJ87dwnU{{<+=Gv= zs`cW|iZ_^G7+tl}sor#~?6`r1%?XFqJ zBk}FWr_{*|53RgiOoCH6gLkBPFFh~5W5S03SbRWA3$S?#sq)>$zT8xLt!3N2&EY1sGlwqTBNksZRS!HFO4e-lOM4*DOK|=Uo7Wf;_>#Jhf3r$9}(^a+g6=9 zyY?+hSm?wnhRG*9IbQ2)Ykd*Xzp-QDtIto)%=e!sCN?YYL9@P-@KzIti%gAD$93=B zOK0S+-kZMV<(6N}3$ELFGnM4tvH0}poUHA#l=hYY*UXnYi>tb(&$C~z9oFyoEM`Nd z@KswAwo5A|jn^;*nDo5<@kio_)B2~5;%RI7fB*b@deYHXx*QL)K1lD1&4!=uN4w*IO^=L49<_%NmYW~YV zpS|*SjW9aq)9bs{FF~{E9TmS+Yi}no7SjTlY-!k(y|E!}Er= zz=TMfd*U5OjRX$2MV@}h$MHgu4MN zHlhDPTJ8|Rti7UzEd`J=_m`bsnBT?S*rJ;(bdYL=fA(wcs-sr3H7+FhP?Y-*+z z9}ltKy$3S?F7em)V-+Pkp9Xv%}Sw%-arD(_{ zF;8gqfi(qt7xu{8&+BQR#WTAD{Kxa6*3U?y_DN7ncbR zGlJ_4DuoqR3Oda65$oO6y;b5$_3NE)ljPp#i*IZ&UR=5+<)7T!EDV~DJrfVk zmt^|#dj0-I@(Q-q-&Bm#&#CY)cr(MQbQRx%9Xl+t_AdPL>T0)(yL&Lh<9!)=o72uJ z`6tLZD=vuV&}3=FNA(DeY4fumQYrc z^CCcNPx!^J(=;0betmiAylK;>6aEv}AAbME;53t8vcl1kQPQr);w7VC=I?KBRcvi# zeciYB3Qo9UQTG0xt#HY^SGkY=nj~LbQ)|(0uDG64C#X^A&92qwnAs==KgZpGr{{W+aWoNhX}?6#`f5_dM$ORJG_ zhNSudmiG-tyfY>A4tpG72$|oxu%)$iqO^J51k>zm0X$9$J-O^^OAm50yw=_klV{+5 zdrAJS;;#Q!ly0Rqw;wmE;quNrB)&31sJ~BU<@Sq5^*m+@De|t%Q%O0xY|fW0nU_oa zc5P%xd~zsbS9MaGFK1CFs+H~wc52~M@_=H=5@M%cEq?$lYjhtdQD)p_=3Kb zD;3U2ZPV-F{8n$Y%>Vl8#=1GS(fkWkRvf<2o^x-{%$WUkb8~KNXiO4f7O;9dVS<3| zL4orc{rl8aRa-+2)GYIvnZz|mN~X59*1sVyE@o}tv%Q|a;yHbD-@N%V*>%zEV{w(I z+O;Qg?mT7a!LZRk*-Pl4p_JH+?nX@>hZokDE?qj&DXi|1lcO_}_rXSAmRdeH)oqjA zW?B?3lH>UN?CfG6@z2W2JgW`{stAeRym^y#L0(Bx#=rC>n~S3*ton;eWG1SKg|Fyo z`Nyqr-0|lPwXlob2M-!CEOe5HHCQF#CZ~4Yu}y+Y_~?yub7gNuE;<&dG%>`=(z4fW z?wqZ=O#d6NNp`Q6hL6uHX;mqPLHcv@&Oe-&Ij_J+#d`;SqPEVb7WN`^66| z!~|zZ_n0pHP`fK&#mm!=&pONAu_`He67f)8Cdz9o`?LDDZ)_g#2#E08|7!)iP;%b1 zcRyy{WLdrN_ZiV->r$s@AC7+eCTkg1b8&lW!tZZC&a=-`-f<_8v#!o{M(%0@_oSzu z&t}YaJ;=E2zzRzRkxBAD|17HW^Ghx`$Mp8j+J9mtr|oX7esJ&FxpmLw^50Ln%)oZ~ zNu065%*Q)sbDqgg;hk69KVLrb-Me{QoPG-DuHHJc^6_LQ8QG7yNpc-2t-F8k4{kZM z{6~wJv_|wyX=#a+vtsh|mmANWd;P$j52Lz3phbTy0~hbKS$w z!v38{udMFZdL}P^;7icjBPu4Ud!z-uf6tkFVAlJ)cl#NlKd{Ed>xH~HXW)KL`k(jQ z?-{qtm<9dcUP`@{CqJL3r=)VLLuQbQP$`cVOZaX}pTx#Ri4vRdUJu`V>9TS16L)#v zdQ~sS68{oigL+6V`vu8k)7P)WLmnIk0t-7 zTV~^B;k!ju)_2sW?XzV*y)xQl_TIhnb*VmCj6V zvt^l7zy11f_yI@w_4nmf)#~b8Eai_KcK%%;qb2FA9W(#J_p>W&%bGd=zIj|+zO?=L zMoF7{-`^GF%rdMoQP1aNpS-{P(f*dG#D%X{rcTl3u-m9wntOTK|0n0>rnl_eu_pS} zJGtH0E*qyWo__u7|6f1u+<*4^cSigAd`qsn`&_3BWb>MDx9!-qPWtTAb1%4#FSyxs zyyS54h4Ui&tm`)&@#B23YF|I6T5jEsH}l0i7#7Qm^xSo*T6C$Y>@A!3+qdltAC$+i zy{>v-wnKL>|GU>sX)VW(Bp2U%y8opS3wO>!QzPzTA%nesZ!PBN-QZr<=&b(UuUmTW z8tuI1+mGx{h}ay9=bTa{Ypo^4GLcPYkL;#{&4)9ao9-7zPB7!zS~HpNdwt3CSSQsG Sp4|)#3=E#GelF{r5}E)q0tuu5 diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 394b5707650277a585ca2ea7cd3bf0d446e86a33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2798 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 m_9a{KcgkZ9tK>0JyA3=E#GelF{r5}E*`xJ=Ig diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 365b4d663a8eae4600393c92446ebcbb629b0d1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6193 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 diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 53d6f5bbdf0b974b55cca892794e00aafdd39502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13447 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 - + + @@ -36,13 +37,14 @@ + tools:ignore="DataExtractionRules,UnusedAttribute"> (ARGUMENT_KEY) val binding = DialogErrorBinding.inflate(layoutInflater) binding.bindErrorDetails(error) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt index 2d83bbbf9..e1c234575 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt @@ -1,6 +1,9 @@ package io.github.wulkanowy.ui.base +import android.content.pm.PackageInfo +import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_ACTIVITIES +import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM @@ -41,9 +44,8 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer ) } - private fun isThemeApplicable(activity: AppCompatActivity) = - activity.packageManager - .getPackageInfo(activity.packageName, GET_ACTIVITIES) + private fun isThemeApplicable(activity: AppCompatActivity): Boolean = + getPackageInfo(activity) .activities .singleOrNull { it.name == activity::class.java.canonicalName } ?.theme @@ -52,4 +54,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black || it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black } + + @Suppress("DEPRECATION") + private fun getPackageInfo(activity: AppCompatActivity): PackageInfo { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + activity.packageManager.getPackageInfo( + activity.packageName, + PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong()) + ) + } else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt index c6fe8a69b..41b97b075 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -6,6 +6,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf import androidx.core.view.get import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint @@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.nickOrName +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -37,10 +39,9 @@ class AccountDetailsFragment : private const val ARGUMENT_KEY = "Data" - fun newInstance(student: Student) = - AccountDetailsFragment().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) } - } + fun newInstance(student: Student) = AccountDetailsFragment().apply { + arguments = bundleOf(ARGUMENT_KEY to student) + } } @Suppress("DEPRECATION") @@ -52,7 +53,7 @@ class AccountDetailsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentAccountDetailsBinding.bind(view) - presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student) + presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) } override fun initView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt index 21a7a492d..6e2bc8c44 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.recyclerview.widget.GridLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.databinding.DialogAccountEditBinding import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -24,12 +26,9 @@ class AccountEditDialog : BaseDialogFragment(), Accoun private const val ARGUMENT_KEY = "student_with_semesters" - fun newInstance(student: Student) = - AccountEditDialog().apply { - arguments = Bundle().apply { - putSerializable(ARGUMENT_KEY, student) - } - } + fun newInstance(student: Student) = AccountEditDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to student) + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -45,7 +44,7 @@ class AccountEditDialog : BaseDialogFragment(), Accoun override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student) + presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY)) } override fun initView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt index 4279102e1..d23978f5f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.StudentWithSemesters @@ -13,6 +14,7 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter import io.github.wulkanowy.ui.modules.account.AccountFragment import io.github.wulkanowy.ui.modules.account.AccountItem import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -30,9 +32,7 @@ class AccountQuickDialog : BaseDialogFragment(), Acco fun newInstance(studentsWithSemesters: List) = AccountQuickDialog().apply { - arguments = Bundle().apply { - putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray()) - } + arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray()) } } @@ -49,8 +49,8 @@ class AccountQuickDialog : BaseDialogFragment(), Acco @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val studentsWithSemesters = - (requireArguments()[STUDENTS_ARGUMENT_KEY] as Array).toList() + val studentsWithSemesters = requireArguments() + .serializable>(STUDENTS_ARGUMENT_KEY).toList() presenter.onAttachView(this, studentsWithSemesters) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index 9b5c63e4c..eab24f91d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogAttendanceBinding import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString class AttendanceDialog : DialogFragment() { @@ -22,16 +24,14 @@ class AttendanceDialog : DialogFragment() { private const val ARGUMENT_KEY = "Item" fun newInstance(exam: Attendance) = AttendanceDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + arguments = bundleOf(ARGUMENT_KEY to exam) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - attendance = getSerializable(ARGUMENT_KEY) as Attendance - } + attendance = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt index 477b762b9..7834b6e8b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.databinding.DialogConferenceBinding import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString class ConferenceDialog : DialogFragment() { @@ -22,16 +24,14 @@ class ConferenceDialog : DialogFragment() { private const val ARGUMENT_KEY = "item" fun newInstance(conference: Conference) = ConferenceDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) } + arguments = bundleOf(ARGUMENT_KEY to conference) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.let { - conference = it.getSerializable(ARGUMENT_KEY) as Conference - } + conference = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( @@ -57,4 +57,4 @@ class ConferenceDialog : DialogFragment() { conferenceDialogAgendaTitle.isVisible = conference.agenda.isNotBlank() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt index 41adc008a..876b563f9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamDialog.kt @@ -4,12 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.openCalendarEventAdd +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString import java.time.LocalTime @@ -24,16 +26,14 @@ class ExamDialog : DialogFragment() { private const val ARGUMENT_KEY = "Item" fun newInstance(exam: Exam) = ExamDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + arguments = bundleOf(ARGUMENT_KEY to exam) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - exam = getSerializable(ARGUMENT_KEY) as Exam - } + exam = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index 34594111f..a1ef2ec5a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade @@ -27,22 +28,19 @@ class GradeDetailsDialog : DialogFragment() { private const val COLOR_THEME_KEY = "Theme" - fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = - GradeDetailsDialog().apply { - arguments = Bundle().apply { - putSerializable(ARGUMENT_KEY, grade) - putSerializable(COLOR_THEME_KEY, colorTheme) - } - } + fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply { + arguments = bundleOf( + ARGUMENT_KEY to grade, + COLOR_THEME_KEY to colorTheme + ) + } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - grade = getSerializable(ARGUMENT_KEY) as Grade - gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme - } + grade = requireArguments().serializable(ARGUMENT_KEY) + gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt index 2af59c011..edc384c5e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsFragment.kt @@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.setOnItemSelectedListener import javax.inject.Inject @@ -48,8 +49,8 @@ class GradeStatisticsFragment : messageContainer = binding.gradeStatisticsRecycler presenter.onAttachView( view = this, - type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType, - subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String, + type = savedInstanceState?.serializable(SAVED_CHART_TYPE), + subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME), ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index f9d463510..5e2cc65dc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -14,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.databinding.DialogHomeworkBinding import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -35,16 +37,14 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew private const val ARGUMENT_KEY = "Item" fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) } + arguments = bundleOf(ARGUMENT_KEY to homework) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - homework = getSerializable(ARGUMENT_KEY) as Homework - } + homework = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index aac60b56d..8f237e537 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -2,8 +2,11 @@ package io.github.wulkanowy.ui.modules.login import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build.VERSION_CODES.TIRAMISU import android.os.Bundle import android.view.MenuItem +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.commit import dagger.hilt.android.AndroidEntryPoint @@ -16,6 +19,9 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.UpdateHelper import javax.inject.Inject @@ -28,6 +34,9 @@ class LoginActivity : BaseActivity(), Logi @Inject lateinit var updateHelper: UpdateHelper + @Inject + lateinit var appInfo: AppInfo + companion object { fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) } @@ -55,7 +64,7 @@ class LoginActivity : BaseActivity(), Logi } override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) onBackPressed() + if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed() return true } @@ -71,6 +80,22 @@ class LoginActivity : BaseActivity(), Logi openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters)) } + fun navigateToNotifications() { + val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU + val isPermissionGranted = ContextCompat.checkSelfPermission( + this, "android.permission.POST_NOTIFICATIONS" + ) == PackageManager.PERMISSION_GRANTED + + if (isNotificationsPermissionRequired && !isPermissionGranted) { + openFragment(NotificationsFragment.newInstance(), clearBackStack = true) + } else navigateToFinish() + } + + fun navigateToFinish() { + startActivity(MainActivity.getStartIntent(this)) + finish() + } + fun onAdvancedLoginClick() { openFragment(LoginAdvancedFragment.newInstance()) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt index 786bbfce8..b9afba986 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverFragment.kt @@ -98,7 +98,7 @@ class LoginRecoverFragment : loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } - loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() } + loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() } } with(bindingLocal.loginRecoverHost) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index c42a4e9d1..03aced14e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -13,10 +13,10 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity -import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -51,7 +51,7 @@ class LoginStudentSelectFragment : binding = FragmentLoginStudentSelectBinding.bind(view) presenter.onAttachView( view = this, - students = requireArguments().getSerializable(ARG_STUDENTS) as List, + students = requireArguments().serializable(ARG_STUDENTS), ) } @@ -79,9 +79,8 @@ class LoginStudentSelectFragment : } } - override fun openMainView() { - startActivity(MainActivity.getStartIntent(requireContext())) - requireActivity().finish() + override fun navigateToNext() { + (requireActivity() as LoginActivity).navigateToNotifications() } override fun showProgress(show: Boolean) { 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 3455b3cf1..5a40a6bc3 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 @@ -100,7 +100,7 @@ class LoginStudentSelectPresenter @Inject constructor( } is Resource.Success -> { syncManager.startOneTimeSyncWorker(quiet = true) - view?.openMainView() + view?.navigateToNext() logRegisterEvent(studentsWithSemesters) } is Resource.Error -> { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index f2acd76c5..8d403271b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -9,7 +9,7 @@ interface LoginStudentSelectView : BaseView { fun updateData(data: List>) - fun openMainView() + fun navigateToNext() fun showProgress(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 36c40d156..ab27ecf3f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -18,11 +18,7 @@ import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.hideSoftInput -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.showSoftInput +import io.github.wulkanowy.utils.* import javax.inject.Inject @AndroidEntryPoint @@ -54,7 +50,7 @@ class LoginSymbolFragment : binding = FragmentLoginSymbolBinding.bind(view) presenter.onAttachView( view = this, - loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData, + loginData = requireArguments().serializable(SAVED_LOGIN_DATA), ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 5cd6fa103..d332ee350 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -6,6 +6,8 @@ import android.os.Build.VERSION_CODES.P import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import androidx.core.view.ViewCompat import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -50,6 +52,8 @@ class MainActivity : BaseActivity(), MainVie @Inject lateinit var appInfo: AppInfo + private var onBackCallback: OnBackPressedCallback? = null + private var accountMenu: MenuItem? = null private val overlayProvider by lazy { ElevationOverlayProvider(this) } @@ -88,6 +92,9 @@ class MainActivity : BaseActivity(), MainVie this.savedInstanceState = savedInstanceState messageContainer = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer + onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) { + presenter.onBackPressed() + } val destination = intent.getStringExtra(EXTRA_START_DESTINATION) ?.takeIf { savedInstanceState == null } @@ -266,6 +273,7 @@ class MainActivity : BaseActivity(), MainVie analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.pushFragment(fragment) + onBackCallback?.isEnabled = !isRootView } override fun popView(depth: Int) { @@ -273,10 +281,7 @@ class MainActivity : BaseActivity(), MainVie analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.safelyPopFragments(depth) - } - - override fun onBackPressed() { - presenter.onBackPressed { super.onBackPressed() } + onBackCallback?.isEnabled = !isRootView } override fun showStudentAvatar(student: Student) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 9c32d8583..458e966d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -139,12 +139,9 @@ class MainPresenter @Inject constructor( return true } - fun onBackPressed(default: () -> Unit) { + fun onBackPressed() { Timber.i("Back pressed in main view") - view?.run { - if (isRootView) default() - else popView() - } + view?.popView() } fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt index 222412ef1..37f9a19b5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -10,6 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.databinding.DialogMailboxChooserBinding import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.parcelableArray import javax.inject.Inject @AndroidEntryPoint @@ -52,8 +53,7 @@ class MailboxChooserDialog : BaseDialogFragment(), presenter.onAttachView( view = this, requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), - mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty() - .toList() as List, + mailboxes = requireArguments().parcelableArray(MAILBOX_KEY).orEmpty().toList(), ) } 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 8c6b0402b..6c54d9fcb 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 @@ -13,6 +13,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.core.content.getSystemService +import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -23,6 +24,7 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.shareText import javax.inject.Inject @@ -66,10 +68,8 @@ class MessagePreviewFragment : companion object { const val MESSAGE_ID_KEY = "message_id" - fun newInstance(message: Message): MessagePreviewFragment { - return MessagePreviewFragment().apply { - arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) } - } + fun newInstance(message: Message) = MessagePreviewFragment().apply { + arguments = bundleOf(MESSAGE_ID_KEY to message) } } @@ -84,8 +84,8 @@ class MessagePreviewFragment : binding = FragmentMessagePreviewBinding.bind(view) messageContainer = binding.messagePreviewContainer presenter.onAttachView( - this, - (savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message + view = this, + message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY), ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index b5f687bd4..14f3d718d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -28,6 +28,7 @@ import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialo import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.nullableSerializable import io.github.wulkanowy.utils.showSoftInput import javax.inject.Inject @@ -108,12 +109,12 @@ class SendMessageActivity : BaseActivity - presenter.onMailboxSelected(bundle.getSerializable(MAILBOX_KEY) as? Mailbox) + presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY)) } } 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 eddb43243..c78ccc6ec 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 @@ -9,6 +9,7 @@ import android.view.View.* import android.widget.CompoundButton import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView +import androidx.core.os.bundleOf import androidx.core.view.updatePadding import androidx.fragment.app.setFragmentResultListener import androidx.recyclerview.widget.LinearLayoutManager @@ -27,6 +28,7 @@ import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.nullableSerializable import javax.inject.Inject @AndroidEntryPoint @@ -43,12 +45,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" - fun newInstance(folder: MessageFolder): MessageTabFragment { - return MessageTabFragment().apply { - arguments = Bundle().apply { - putString(MESSAGE_TAB_FOLDER_ID, folder.name) - } - } + fun newInstance(folder: MessageFolder) = MessageTabFragment().apply { + arguments = bundleOf(MESSAGE_TAB_FOLDER_ID to folder.name) } } @@ -131,7 +129,7 @@ class MessageTabFragment : BaseFragment(R.layout.frag setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle -> presenter.onMailboxSelected( - mailbox = bundle.getSerializable(MailboxChooserDialog.MAILBOX_KEY) as? Mailbox, + mailbox = bundle.nullableSerializable(MailboxChooserDialog.MAILBOX_KEY), ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt index 5811456b6..e46ab42cc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note @@ -13,6 +14,7 @@ import io.github.wulkanowy.databinding.DialogNoteBinding import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString class NoteDialog : DialogFragment() { @@ -25,17 +27,15 @@ class NoteDialog : DialogFragment() { private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Note) = NoteDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + fun newInstance(note: Note) = NoteDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to note) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - note = getSerializable(ARGUMENT_KEY) as Note - } + note = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt new file mode 100644 index 000000000..163ba8cdf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.ui.modules.notifications + +import android.os.Bundle +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission +import androidx.appcompat.app.AlertDialog +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentNotificationsBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.utils.openNotificationSettings + +@AndroidEntryPoint +class NotificationsFragment : + BaseFragment(R.layout.fragment_notifications) { + + private val permission = "android.permission.POST_NOTIFICATIONS" + + private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) { + if (it) { + navigateToFinish() + } else showSettingsDialog() + } + + companion object { + fun newInstance() = NotificationsFragment() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentNotificationsBinding.bind(view) + initView() + } + + private fun initView() { + with(binding) { + notificationsSkip.setOnClickListener { navigateToFinish() } + notificationsEnable.setOnClickListener { requestPermission() } + } + } + + private fun showSettingsDialog() { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.notifications_header_title) + .setMessage(R.string.notifications_header_description) + .setNegativeButton(R.string.notifications_skip) { dialog, _ -> + dialog.dismiss() + navigateToFinish() + } + .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> + requireActivity().openNotificationSettings() + } + .show() + } + + private fun requestPermission() { + requestPermissionLauncher.launch(permission) + } + + private fun navigateToFinish() { + (requireActivity() as LoginActivity).navigateToFinish() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt index 7dcd51cea..0a71afef1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.core.text.parseAsHtml import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString class SchoolAnnouncementDialog : DialogFragment() { @@ -21,17 +23,15 @@ class SchoolAnnouncementDialog : DialogFragment() { private const val ARGUMENT_KEY = "item" - fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + fun newInstance(announcement: SchoolAnnouncement) = SchoolAnnouncementDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to announcement) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement - } + announcement = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 364ad2137..77a3c6cf4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -1,18 +1,16 @@ package io.github.wulkanowy.ui.modules.settings.notifications -import android.annotation.SuppressLint import android.content.Intent import android.content.SharedPreferences -import android.net.Uri -import android.os.Build +import android.content.pm.PackageManager import android.os.Bundle -import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat @@ -26,7 +24,7 @@ import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openInternetBrowser -import timber.log.Timber +import io.github.wulkanowy.utils.openNotificationSettings import javax.inject.Inject @AndroidEntryPoint @@ -42,7 +40,14 @@ class NotificationsFragment : PreferenceFragmentCompat(), override val titleStringId get() = R.string.pref_settings_notifications_title + private val notificationsPermission = "android.permission.POST_NOTIFICATIONS" + override val isNotificationPermissionGranted: Boolean + get() = ContextCompat.checkSelfPermission( + requireContext(), notificationsPermission + ) == PackageManager.PERMISSION_GRANTED + + override val isNotificationPiggybackPermissionGranted: Boolean get() { val packageNameList = NotificationManagerCompat.getEnabledListenerPackages(requireContext()) @@ -51,6 +56,13 @@ class NotificationsFragment : PreferenceFragmentCompat(), return appPackageName in packageNameList } + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + presenter.onNotificationsPermissionResult() + } else openNotificationsPermissionDialog() + } + private val notificationSettingsPiggybackContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { presenter.onNotificationPiggybackPermissionResult() @@ -156,25 +168,29 @@ class NotificationsFragment : PreferenceFragmentCompat(), .show() } - @SuppressLint("InlinedApi") override fun openSystemSettings() { - val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) { - Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - putExtra("android.provider.extra.APP_PACKAGE", requireActivity().packageName) - } - } else { - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", requireActivity().packageName, null) - } - } - try { - requireActivity().startActivity(intent) - } catch (e: Exception) { - Timber.e(e) - } + requireActivity().openNotificationSettings() } - override fun openNotificationPermissionDialog() { + override fun requestNotificationPermissions() { + requestPermissionLauncher.launch(notificationsPermission) + } + + override fun openNotificationsPermissionDialog() { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.notifications_header_title) + .setMessage(R.string.notifications_header_description) + .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> + requireActivity().openNotificationSettings() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + setNotificationPreferencesChecked(false) + } + .setOnDismissListener { setNotificationPreferencesChecked(false) } + .show() + } + + override fun openNotificationPiggyBackPermissionDialog() { AlertDialog.Builder(requireContext()) .setTitle(getString(R.string.pref_notification_piggyback_popup_title)) .setMessage(getString(R.string.pref_notification_piggyback_popup_description)) @@ -202,6 +218,11 @@ class NotificationsFragment : PreferenceFragmentCompat(), .show() } + override fun setNotificationPreferencesChecked(isChecked: Boolean) { + findPreference(getString(R.string.pref_key_notifications_enable))?.isChecked = + isChecked + } + override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) { findPreference(getString(R.string.pref_key_notifications_piggyback))?.isChecked = isChecked diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt index 4cbdac945..232b03480 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt @@ -26,12 +26,13 @@ class NotificationsPresenter @Inject constructor( with(view) { enableNotification( - preferencesRepository.notificationsEnableKey, - preferencesRepository.isServiceEnabled + notificationKey = preferencesRepository.notificationsEnableKey, + enable = preferencesRepository.isServiceEnabled ) initView(appInfo.isDebug) } + checkNotificationsPermissionState() checkNotificationPiggybackState() Timber.i("Settings notifications view was initialized") @@ -49,12 +50,17 @@ class NotificationsPresenter @Inject constructor( view?.openNotificationExactAlarmSettings() } } + notificationsEnableKey -> { + if (isNotificationsEnable && view?.isNotificationPermissionGranted == false) { + view?.requestNotificationPermissions() + } + } isDebugNotificationEnableKey -> { chuckerCollector.showNotification = isDebugNotificationEnable } isNotificationPiggybackEnabledKey -> { - if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) { - view?.openNotificationPermissionDialog() + if (isNotificationPiggybackEnabled && view?.isNotificationPiggybackPermissionGranted == false) { + view?.openNotificationPiggyBackPermissionDialog() } } } @@ -70,9 +76,15 @@ class NotificationsPresenter @Inject constructor( view?.openSystemSettings() } + fun onNotificationsPermissionResult() { + view?.run { + setNotificationPreferencesChecked(isNotificationPermissionGranted) + } + } + fun onNotificationPiggybackPermissionResult() { view?.run { - setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted) + setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted) } } @@ -80,10 +92,18 @@ class NotificationsPresenter @Inject constructor( view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms()) } + private fun checkNotificationsPermissionState() { + if (preferencesRepository.isNotificationsEnable) { + view?.run { + setNotificationPreferencesChecked(isNotificationPermissionGranted) + } + } + } + private fun checkNotificationPiggybackState() { if (preferencesRepository.isNotificationPiggybackEnabled) { view?.run { - setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted) + setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt index 2bf8e31f4..a391681cb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsView.kt @@ -6,6 +6,8 @@ interface NotificationsView : BaseView { val isNotificationPermissionGranted: Boolean + val isNotificationPiggybackPermissionGranted: Boolean + fun initView(showDebugNotificationSwitch: Boolean) fun showFixSyncDialog() @@ -14,10 +16,16 @@ interface NotificationsView : BaseView { fun enableNotification(notificationKey: String, enable: Boolean) - fun openNotificationPermissionDialog() + fun requestNotificationPermissions() + + fun openNotificationsPermissionDialog() + + fun openNotificationPiggyBackPermissionDialog() fun openNotificationExactAlarmSettings() + fun setNotificationPreferencesChecked(isChecked: Boolean) + fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt index 6ff7a39b7..598046a2d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt @@ -8,6 +8,7 @@ import android.view.MenuInflater import android.view.View import android.widget.Toast import androidx.core.content.getSystemService +import androidx.core.os.bundleOf import androidx.core.view.get import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -24,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nullableSerializable +import io.github.wulkanowy.utils.serializable import javax.inject.Inject @AndroidEntryPoint @@ -38,7 +41,9 @@ class StudentInfoFragment : lateinit var studentInfoAdapter: StudentInfoAdapter override val titleStringId: Int - get() = when (requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as? StudentInfoView.Type) { + get() = when ( + requireArguments().nullableSerializable(INFO_TYPE_ARGUMENT_KEY) + ) { StudentInfoView.Type.PERSONAL -> R.string.account_personal_data StudentInfoView.Type.CONTACT -> R.string.account_contact StudentInfoView.Type.ADDRESS -> R.string.account_address @@ -58,10 +63,10 @@ class StudentInfoFragment : fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) = StudentInfoFragment().apply { - arguments = Bundle().apply { - putSerializable(INFO_TYPE_ARGUMENT_KEY, type) - putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters) - } + arguments = bundleOf( + INFO_TYPE_ARGUMENT_KEY to type, + STUDENT_ARGUMENT_KEY to studentWithSemesters + ) } } @@ -75,9 +80,9 @@ class StudentInfoFragment : super.onViewCreated(view, savedInstanceState) binding = FragmentStudentInfoBinding.bind(view) presenter.onAttachView( - this, - requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type, - requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters + view = this, + type = requireArguments().serializable(INFO_TYPE_ARGUMENT_KEY), + studentWithSemesters = requireArguments().serializable(STUDENT_ARGUMENT_KEY), ) } @@ -154,7 +159,6 @@ class StudentInfoFragment : ) } - @OptIn(ExperimentalStdlibApi::class) override fun showFamilyTypeData(studentInfo: StudentInfo) { val items = buildList { add(studentInfo.firstGuardian?.let { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index c9243b12e..4f5547d20 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -8,14 +8,12 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.DialogTimetableBinding -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lifecycleAwareVariable -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import java.time.Instant class TimetableDialog : DialogFragment() { @@ -28,17 +26,15 @@ class TimetableDialog : DialogFragment() { private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: Timetable) = TimetableDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + fun newInstance(lesson: Timetable) = TimetableDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to lesson) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - lesson = getSerializable(ARGUMENT_KEY) as Timetable - } + lesson = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index 6fd126326..e95d6f827 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import androidx.core.os.bundleOf import androidx.core.text.parseAsHtml import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint @@ -39,9 +40,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE" fun newInstance(date: LocalDate? = null) = TimetableFragment().apply { - arguments = Bundle().apply { - date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) } - } + arguments = date?.let { bundleOf(ARGUMENT_DATE_KEY to it.toEpochDay()) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt index 7d32278f0..ddd7488e4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt @@ -4,10 +4,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.databinding.DialogLessonCompletedBinding import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.serializable class CompletedLessonDialog : DialogFragment() { @@ -19,17 +21,15 @@ class CompletedLessonDialog : DialogFragment() { private const val ARGUMENT_KEY = "Item" - fun newInstance(exam: CompletedLesson) = CompletedLessonDialog().apply { - arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } + fun newInstance(lesson: CompletedLesson) = CompletedLessonDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to lesson) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, 0) - arguments?.run { - completedLesson = getSerializable(ARGUMENT_KEY) as CompletedLesson - } + completedLesson = requireArguments().serializable(ARGUMENT_KEY) } override fun onCreateView( diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt new file mode 100644 index 000000000..d0d47025e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.utils + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import java.io.Serializable + +inline fun Bundle.serializable(key: String): T = when { + Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!! + else -> @Suppress("DEPRECATION") getSerializable(key) as T +} + +inline fun Bundle.nullableSerializable(key: String): T? = when { + Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializable(key) as T? +} + +@Suppress("DEPRECATION", "UNCHECKED_CAST") +inline fun Bundle.parcelableArray(key: String): Array? = when { + Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java) + else -> getParcelableArray(key) as Array? +} + +inline fun Intent.serializable(key: String): T = when { + Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!! + else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T +} + +inline fun Intent.nullableSerializable(key: String): T? = when { + Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T? +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt index 1ef03f2e6..62b85af4d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/IntentUtils.kt @@ -1,11 +1,15 @@ package io.github.wulkanowy.utils +import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.provider.CalendarContract +import android.provider.Settings import io.github.wulkanowy.BuildConfig +import timber.log.Timber import java.time.LocalDateTime import java.time.ZoneId @@ -86,6 +90,23 @@ fun Context.openDialer(phone: String) { } } +fun Activity.openNotificationSettings() { + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra("android.provider.extra.APP_PACKAGE", packageName) + } + } else { + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + } + } + try { + startActivity(intent) + } catch (e: Exception) { + Timber.e(e) + } +} + fun Context.shareText(text: String, subject: String?) { val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND diff --git a/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml new file mode 100644 index 000000000..b1b01a0b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_dev_mono.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground_mono.xml b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml new file mode 100644 index 000000000..e2e747316 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_mono.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml new file mode 100644 index 000000000..8e506e1e7 --- /dev/null +++ b/app/src/main/res/layout/fragment_notifications.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index d59ec8e17..da1bca126 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ - \ No newline at end of file + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index d59ec8e17..000000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 85f6a2c87d16f05dac43a09867994b07f527ed7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4025 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84mJh`hS0a0-5D788$4YcLn>}Pjo}UnDV35p zJ)<%){8aFa#J0qH5}lD*Go~v2ybf~+P)Yq=vs&V_wo02z6v$I#uoYru{ zd5Zg@2iA@WY|aO^cQh^FJaIVj&-?Fpcb4xlKGQ0>!`Q+4dCl`fbASJ+-~apfy#N1Y z8yAS%yC_IFoOa+_xKI4g{+fWBEjMmBRxe)k?$Fw`&o`d`d6==y$Kh!~_S@fH8-Kj} z)WMNIH%O90$jMvb-QoAUiqF=4Oq^#`9UqypBz&ct@k$e$nw5TwZ}JogJlW#6cHQMH z6LZH~89P^ou6(uPeb4cnYpw122a7*Sy~)0H`dHlm3;x$q*bcN!WH?Y0V8pwGZHl^v zY8h9tTxsuz4V@Nr9@Lp7ROIeqzt^2xSKAl(MxA|`0IR>bd-^#MH%Dhr^Bc*>RHPmV zExyCZ{hsstHnU}Mr3ZMjy{=>_2G~#J?RgsfJo>|}qhbyZB?@$=Jud#WTtVs3(IZk@ zf4yh$>7Hhq;m&lJRixNo!dtuV|H67qwfA%9c6oU;knsI z=iIcKCj4&ogDDYxfos*;r=CieIi&KesbI7EFJX(E2^!m&Ju0=Xn#4IKX?XsgbL}5n zpQ^|4gLgKGb``{(={>2ab-b@M+44ypd!MSugDvOZhpe=@VfBq;L6X%J>#K_G?ZUY( zEEn(9+pDTBb{D?8qi~6i)vQ~O{@WJb`5n6IF4HdYtIC&|y!O5GDcF*HY{KSl@yVy- zYh7Oj2YbG~wswi0;lwLBw_Kvm%~xk@ZrlET3+LV4xhv)9yUFI*!rDe=KxwvO1ZC+{Ux7uUz z$+D6wTK&DMy{)ZxncVN+^2+L$Rkl88Qhws;i;}ly=5E{fa=EVa<(s|mreUvI(D7AM zWK*MjqjI+&a6A_J?}xIdy!|{CMhgudk8fp+0q5f%?#VT}c|@;PP`hYtxk3LPa|6G_MMe84Zoa{xBX@3uH0M_sgNQ4(+P!gmz8tezXtXEO=y2+ z6D};Yrl}?2%JgZIxM$9MqU2)E^7P0oQx$u?K9$3jF%LG(oRZHzxnw~$;|2{)lM+Ss z?u^^g4D8D+Rxi0=`$b@!wbfct$h$4bx)n;>}mjeA5;& zeb2)BbCaQAcieB0F3%|SCw9{=t|>Yx6!h!qbd~B~CA()PaeHht;4D_^Nt1S6BymJ( zUT#{?S=j)s=@zH=tXL$>;Fuo8qw|aPY=HpaD<=JB_62?m!-Gf^wjm|y@@E&)$X3t#5BdQ@e#YcifaB|SFeR0w!4$rGk2Et zesUL`@LokzWKYADnL7<1+WD&8PJ7lK;qAlL*nICjPs6EYzRG8p`%SFX-;-d}$o+o5 z|1Cx9Z9N(0>thsm7C-xtmKRegbXohO|1S1dKhBHpfZwT|Q5i-FHiJzc((>7c>n#c7ikg(@m4 zcU^rY3sb>xs2gO_C$>>Km3Z{n9W~%Ijdj~+unOi-fR_CU*S}IeR2P! zzw7rnsWzXOpxw@2%ycCp)OAz(nFqG438#dX71*u_GTkpJu;3lTg{*}EuQ(bEk`j%3 z*5=&&_4Nuvef0LeEmy8j-c$K`#s9+Fk+xxcf&KmsH#8Zf7u5gwoo&UiCFzPrV5isB z1KT_2I{atiyZOX_)@;vrcXq1qp5kF)|0~GU%Bi%e z-#(u|`Pu65wM->7Ka1KKrf}R2RSFbJO1$=I0r%#EFN0q0|DhA6J->e5maErh8LMh` zCY?Iy+HI9`#+50KlycXa@z5*yt}Ui8tSHq4v5yMVcsDm*zH_&kYs#steM*sU&)b(9^tTm1 z_c*r1Gv)l7o6FAJy0wP&TGk0OAGN^FrXLl-)!SZvN{#y)pk$H3@?yWxNzt<#5*IT4 zvK8FCPU)_K)IR@(9?y^RJF?wnd3Zah@{4Jtq2ZIHuxY+$tL}=&2Y978PP~1f=*^Gv zcgE*GT;fXnbKYyAfwVz^d2)ZNywomNrOLVe0rB6i1iRe7a^>LpIe*?+YXxn+WpTD= zaao+_VGF{pe>-xf zgx}uhRiI>d<@#-HY+AzWnBYW#tDeag zzlX27xL3zm+}G#|*I+w*%-C7&bzQpP9QAFcYkZ&2@%C3|kE^|!_RYZOV4bN}vqQqg zEKB?N;wK$9byn|v#_FjQsVAbg=$#4MTt8d>zsvXkpX}=J@}0>C?Pq5cb;UdtHcK&U ze_!l9UC8!I^0za~9?3}llA6P2HG5K0so>$6Pf{kC<;~gqW@qo4n<`T$Mfjh;xng(d z|HJ=Wr0-w+baLA1?>9~>#r(O#V0U=dB=hX771uPcCB9hkc5YExSIC^72eKor*{u6l zPMlble*VFn@6G4-U1U0VH+J2-1&eQP*#7r#GWXjALA~j#e|-r)_p_|#l&gGwPFC>^ zZUN!50bKS!8n&34mA$cHGA_JyaoRDd!}EF%%(IM(t9P6$-td28W^X`9!3TjGKbCqd zU3uf>_Eg5uqjz>L5&bmz+>|xvPA_KLu5NbCBs+0+g8RfkzR5mw`kqw@w940cxbF2^ zUA{A>f8X=w1qyR~zaE~q=dJb`7N#$*hnH1V^w_TmG0WK9k+Gv@-NCj}JBg#09!BI` z3(vcmB;Opi-Yi%^``7E#)N9o@4ydGPhesa~*{L$At)u$=+?HK#rBADM?DZugpL}*L zD6m;~=u*)SC0&=buk!b8v`W3aFqiA5;bV`D23ynAVv2HZ8WpoOT(;X#pr|gq@xsxD z1WqBf7Ehzd-)SeR<34|lZL~VZ#&YNK+}3r>OYYsX_^aqO$!oL0s;Lo%rm|N;eZ{)@ zcg)iYns0lf%XhZbm8_VB^DnxT|i++!!-3(?P<=e7%V zCVuM>)Nj^nOzyS%_)VFWdC{G_cN3-mggG2oR3o)SsA{cYcHQhZFP(k7mYnTkd>$Ep z-#mc*>W7DNOzeM}4mLB@IaJHG`Q|phVwJFYM`lFlH?~yrZ8O;BxaF6O%%=+d_4W^E z9PUXj{}FwG`MBhKrwak%(+%xAj^1o%;F)&v?mJzzi%*$TgX|qR86!XGPk7++C_?t^ z96meELx-8~t9@e+tdkJ=@;+zgoAQ{2N7=dXX!~oSDZo?-??$tSVf}ISom_?FAJM=Yg^mqrA&SoYm~*}E6oiI9u_9=W}3dH z!_9%|hIqu`uaYWlEInq{EM9_4S3g-dHl8-?I2tLLm>9ZpfsEOSK+j!oFRZ-q`PmiA z8$X+UcD!2r;K3P-`v0kp$Gl`Dm7R)(xLIcW`PzM7NUEiE-Gvh?90X!EF6ELjN;wqo z-q#Z?B(UMt-vtZa&gW`v-_OIj@<+c*&|W)}E&qOBxFD_Z#5qGhW!Ct|2OpW~C znlc|;ILUs|sO#SI?&A+OR`40IuK(!Hv^4m5UssYke>C5TwuHt64z7Q_zhCIxKX9*P c$A9LUpi32cMyHh+7#J8lUHx3vIVCg!0O(|(%K!iX diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index dc6ac682d014b03ff5e5f9c69601f49e8f80af49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2569 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

BGkC>b@iFtX&e9^s;Pm}>dduH> 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 Rj)8%J!PC{xWt~$(69Ab{{bv9G diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b7d82f5d5998bc0b61e3a8d6314c52bbe2d1198f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8796 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 AB>(^b diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 7ad000eece434268e4e5e443425f4a68f11eb05a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12531 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 Student is already signed in Standard + + Enable notifications + Enable notifications so you don\'t miss message from teacher or new grade + Skip + Enable Account manager Log in diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 000000000..418b6d4c4 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + 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 e52ec3ae2..a31ef5177 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 @@ -90,14 +90,14 @@ class LoginStudentSelectPresenterTest { studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) } just Runs - every { loginStudentSelectView.openMainView() } just Runs + every { loginStudentSelectView.navigateToNext() } just Runs presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false) presenter.onSignIn() verify { loginStudentSelectView.showContent(false) } verify { loginStudentSelectView.showProgress(true) } - verify { loginStudentSelectView.openMainView() } + verify { loginStudentSelectView.navigateToNext() } } @Test From 5d5dfd4eb4173ed6dfe45b838592858e1ed3ec04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 18:38:59 +0000 Subject: [PATCH 072/545] Bump mockk from 1.13.2 to 1.13.3 (#2067) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index cf7223bae..76306c1bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.4.3" chucker = "3.5.2" - mockk = "1.13.2" + mockk = "1.13.3" coroutines = "1.6.4" } From 083ca34f1b6f1bb14f95b2444e4ec8a74f9f12e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 13:32:46 +0000 Subject: [PATCH 073/545] Bump hianalytics from 6.8.0.300 to 6.9.0.300 (#2069) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 76306c1bd..0dba01452 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -249,7 +249,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:21.3.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From b93c0222a29ace3d0d8b9f04ff5a90f6960b56dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 5 Dec 2022 15:44:30 +0100 Subject: [PATCH 074/545] Fix conference details strings (#2070) --- app/src/main/res/layout/dialog_conference.xml | 4 ++-- app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_conference.xml b/app/src/main/res/layout/dialog_conference.xml index d08edf4f7..b6e811ae6 100644 --- a/app/src/main/res/layout/dialog_conference.xml +++ b/app/src/main/res/layout/dialog_conference.xml @@ -40,7 +40,7 @@ android:layout_marginStart="0dp" android:layout_marginTop="28dp" android:layout_marginEnd="24dp" - android:text="@string/all_title" + android:text="@string/conference_place" android:textColor="?android:textColorSecondary" android:textSize="12sp" app:layout_constraintEnd_toEndOf="parent" @@ -71,7 +71,7 @@ android:layout_marginStart="0dp" android:layout_marginTop="16dp" android:layout_marginEnd="24dp" - android:text="@string/all_subject" + android:text="@string/conference_topic" android:textColor="?android:textColorSecondary" android:textSize="12sp" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 874b121f6..26ab51efa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -422,6 +422,8 @@ Present at conference Agenda + Place + Topic School announcements No school announcements From df5155f1c78d6a59fa3ccae91fbe726f454c4b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 5 Dec 2022 15:45:07 +0100 Subject: [PATCH 075/545] Fix a typo in excuse message subject (#2071) --- .../wulkanowy/ui/modules/message/send/SendMessagePresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index 5ab8f8fc9..e776e9941 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -55,7 +55,7 @@ class SendMessagePresenter @Inject constructor( view.showMessageBackupDialog() } reason?.let { - setSubject("Usprawiedliwenie") + setSubject("Usprawiedliwienie") setContent(it) } message?.let { From 217ebfc5492d12c46d580494b0226a75452296b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 14 Dec 2022 22:41:57 +0100 Subject: [PATCH 076/545] Fix app name in french (#2072) --- app/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0dba01452..3f8830dc9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -237,7 +237,7 @@ dependencies { implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:2.2.2" - implementation "io.github.wulkanowy:AppKillerManager:3.0.0" + implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 delta 8722 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 Date: Wed, 14 Dec 2022 21:42:21 +0000 Subject: [PATCH 077/545] Bump fragment-ktx from 1.5.4 to 1.5.5 (#2077) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3f8830dc9..83fe72f1f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.6.1" implementation "androidx.appcompat:appcompat:1.5.1" - implementation "androidx.fragment:fragment-ktx:1.5.4" + implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.annotation:annotation:1.5.0" implementation "androidx.preference:preference-ktx:1.2.0" From 4a5991ade476e280e3234ba1d2fb1f4c6286074e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 21:43:04 +0000 Subject: [PATCH 078/545] Bump hianalytics from 6.9.0.300 to 6.9.0.301 (#2080) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 83fe72f1f..378b9db90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -249,7 +249,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:21.3.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From fba4e8531195b7cc217692d46a2ae50521556eab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 21:52:41 +0000 Subject: [PATCH 079/545] Bump firebase-bom from 31.1.0 to 31.1.1 (#2079) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 378b9db90..a61f0f795 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' - playImplementation platform('com.google.firebase:firebase-bom:31.1.0') + playImplementation platform('com.google.firebase:firebase-bom:31.1.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From f1479d489b94d87960ad063afdec35d06af158f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 12:28:26 +0000 Subject: [PATCH 080/545] Bump play-services-ads from 21.3.0 to 21.4.0 (#2083) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a61f0f795..8ce6de696 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -247,7 +247,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' 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:21.3.0' + playImplementation 'com.google.android.gms:play-services-ads:21.4.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' From 277ffd22be786cfa429ea1d61827e8cb62d1ea56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 14 Dec 2022 22:41:57 +0100 Subject: [PATCH 081/545] Fix app name in french (#2072) --- app/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f6a82ad20..fdb844aca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -237,7 +237,7 @@ dependencies { implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:2.2.2" - implementation "io.github.wulkanowy:AppKillerManager:3.0.0" + implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 zcmbPmk$LT9<_#=XtSSLBqeCZiTItn?SJsGx%l_Z@#_)7XgUOT&thbhl-9GCmma$Mp zfJLb5qCf^y%MuQsGWFS>Y3bK)MlyX}a4V|6>bmw?EPjKcDQaeE;{|{qNta^Uwc%ez2Wk%|ZKT3nj()HRa+?G@o^z z#a{33#%(0TcbX$H!*E`x+8lid*+u?#Xa*<_c|Vv{-}1ZvRL)q!RQ$u*6yi^R!yp? zR(({#&Y!G!=k)FOQ$FhLi9W^dfF5oWYsIVdY#^byTbI z>C-JGGcw&xd6wN?F?-i$&j;sD% zWqivkZTK8;_hjbH)witH7tgNy7Ub~Kz*i`L#mg;jr&p(aOgWlWbj!bfl7@Ha#9f!3 zXzg;pX13(0n@Y{4skVEICoXxKDn4nF!A;L)%L<-!8^^HJ>dp(gwXyj2#2M30J&o{N z^{peV`pIWs-|pqN*9KnRaK*y#oA9+uuNGX3>oPsMF=+0s#Ip)ElRk&eE3q)FS(@s8aqVMtZj)|dU zW7=#<<25FZneESbA`3a^hkal;CYp2jESK4$(1U7n^P10cZA)0)WVS{7MxV|}t&pV; zOzO)xbu?RFT}jw-?Z3w z?MRhZb?kp!E>ySv0soK1P3j-iS?iC*ckJLX;0(+SJJxZ1*0kW+hWVF+w}v=u?Q@-+ zA;eLdopsA7+T&IH&4>FRzUP{EWXXX`FJF|V`?&jO1v^=WJvrpZ_vzEkuBlt5C%HX1 z73rv4@K<$OhF$&3o!4A*ckhr{@zN*1#6rqc@ANtMlUr9PczNrun-Ul#E2n=^k8hd%w!nl`a;OYACRm-@!QWJ6tF@vB<9 zRtXjFYwEf?!#Vxj%Gk46(&@p`XHMRGQhD7V|7Xm!=elQ888=n;EtBnCS2REEcB}Xi zy-#Np|C^XbCVb^xv$OQvtZv1PD|+~xg1W-l9#$4wE(vIAF3i)IawawPivRs^q5GlL zIdk(KdwgVGxb92tl=9cd(s?rKf6A0cb+&L{Q!03_H{<33*?lVvPPHVeF7u5(y)1Xm z?o8RA4LLhJ=FjGf(6IYjS?YPDtMRZWN8sDwz3;cZNR14d)+!MD{Y=_)gYz{uEnik1 z3m4Y3IdAT=Xky!ylIdkFRkQDxEZ@UuzL8P;d6Gl@&u_8otgHSm`CuFJVaaUH^)%s_KVBXFSNP-g;l@IiSBDQ4vb^?p-c>(eL;Hh`RPz!3OFwF)vNQTs76`{S z{60GCh2uegCM#vzea##j=5hVhajN~wzd)(GGF|ceT(_m$gEq~G-`mipBQ|eYx>< zn|9>zyq4ayLhf+ZErC5RWp!p9cxo-BzM+CgaaNY!yhB2>)}K)F`Qlw|yYT9Fr%&?j z_pf_p|7V$2TCXdA_{17Do%u!!kIH|0?$XH_GgEo(zAlciv_ezC)WFxWafcrr{%2e< zjX!a}`rBLg_8b+CxcF$EUGxG~0lqm0OPP{0p00n*YOC+8dr(MKFi41R((HMrmaY#U zu)M13|6gvTZaKlVR*ZK||G$?T49f!^u8ggeX^&!3U9?!`(&h>Et>NoGP1|>;sC42X zrfsG(4H%EhChg*~g~Gv2fnJ ztNqCyHk`44gbzPrnxwakU2*cVRFztD#UCtJugI;^vp<=qpu3i1(vs+l-rHCuu3Elv zKa%`nad2Z`A^+8DYmZd>-0A)MWGcUZXVxADc8^n)Cc7pvTX@89U93)T*AUXGf4iow zhVi#bi%N-oFY9OX$+3MC3t)l+!mfOrf>jS)* zS-2P&I5-#>7#tbaJa}-tmWhEul8u2uck*&4+xojq*Yf{3t-K_ba=KlGCD8e5LeLkc z%PjAgoemJ&rh7B@YW)*l@mU@wvnpP;+MlXOE6em=xzP8radFx6+H=)qY2VM^m*3Bz zlW@~PrcyFZNF*(_LgTWfEUV=?X^#ZQ<4VneUw`yzn@O1-G>E$O{Qb_SCTphmX@|W$ zq*s1tnz~ti>-*1IYdzStW8&6FMf;v#8J;;+Ze5Z={MEl|<#!*=+rS?ZB_4P2=|26M zwykd>OV?fbuqrI!(60v{4?91MR8+m}yVxpf<3?xKV_tr-d-ASo9jZR9y{_o$hMm#R z<|%*vuqe*jaP4p1+sj%KRjYR$PF!_d-nw-8&z8hh?3b3WG@4nT`_3|?a0XY~ z-&iHJE6zH*H`ycCbZ6~`ll+I5@vO5tc}ARzXYwf>GD& z!mSGnE~{%)UYho5^<2%#$HEprz3DXVt;_~XPQyahaNnLsO`O7-!fcb&@*Yj=Q*C?6 zC_8gU+iN46B#HMG)xkH#%jzd5KKd`Q)ic%SlJwLC;`&WnN*p$C+2WksR8+)nwQ!2A z<6_B#KQ1-1pYC`3za~Un^Vn0_ZC&0b=FOoy)=0GFY?4cI)THS?vANzh-D?RTF_OiKq?@gYewZF`p z;;^GK$F9D+tlq7#CGbB3QkoQCaAXKO)6%(5lz~B9pMgPPvY?ghlVeK9j{gW?rUY?Qm+_KjDXjHa%b|&|=sOetfQ3tQF z^l)XpzE*p6TiN!t+qQ14-kP!R|IYk0BTu%*e|Gg1?`Kz^xBag3zxdrwbN~JS4jgs~ zc|2>)WjQKN_R! z+k4KtVx?dCXQRw%*?XLp&#c*ZVf~dI*Upz(*&bPKQa9Nz{bSZ#xo6xdPtJY{)sN3M z$-AI=BGqdBQ@6QN6?6aW37GZul8Nu4{s%|^{;P%p;F0*0}9iOsPw(i}O ziF&=dy}SS3dHg(1lH=u;HvOs3jONyzPwZ8ea(`;P?6T{f<(Jv&+K&~^HRJrwJLkh@ zUHeIwOfqYB`rWSBe(bLm{|C`aF)M>-_v{H?J~!ss^K#Gn|9P3SO7?ZF|MuC`D*p8Q zlaF6l^sgw{JO94+UtXmPzfJZ9E|1%orN6&k$oRg>S$30u3;YECP00A1`16+P74z&r zg1z|{H5IuZ{RxcR8lGXdY_oUGMeVb4|8^Xk|KYRU+;va7Q}t)m?fAx+<$_*raFSg{?w;kr)8}>Z)Rr-}K^OmzNhrA3}ubHh|egE2?uama=u8rPq zvi9b-t(n26RcF86zApV#{=O@F?yTXOeR1BZFl)K4Z&wU`^Ex>9uh@H+M_t$c{>dlY z$E&K=SX`d-EMm{{RqA89t;p53|m(R9gm>&MeFqqChk>t|=C`tjbHBggla@2kq=WwKqauf1fJ zwiE%t_;+$)b$?{hyITo!4sy|QWbw%kl6Pe(J)?DdhGb1Jiq z4w=~MtXh9|)|DT6Jm&rKX;D>P+WIp#a9L+>`g|+EM)}H$b8WY3+*kP59iDce^Q!u3 zA*uVk+Y{el(alVX5v~`ndJvL)0WRQ zIn1S1*%@JUFLPzXjFhk6BkSjGySg;(Pa^x|0Mi-L{x?@uv+63$-Bql!((V3nwY3Z9 zt;ke=e52;0W`*wtwuVU?-W;n-I4{PWx9!oy03}7i1__z-yj{Ckx;Gm?=3B95o$j=o zPgO3r+!gh{CAnwS+UrLj30~eN+Nj?1gx#)I*5gU!u{$#To>6g=d0J)t?i@Esu2*V2 z6OnuC{Y^vEMwf2Q1E*haIy7y`?Fzo%4FTRNS3fdoems5uCU@y0u?E}Z^mR55+3uKD zN$UBzt>%nVB`Vfv)z(Q*MaOJ+VwdT>3{=R{ENJ1?}xdp?$xUte-zKTPQ43 zuqFE7{d!rk<*%199d0_>EpGhviOrnL>OcRdSCxL%pSCP&)~rCyXGQKRa%+Ui8rFM?MvAC1Pv#>C~Oh)Ums~`QX+4DJ9dV zZt}h>sjO(t=;O6>EEyUcI^LMK{}BCwbEsX@^)}c(dI<-Ogio=6mL! z)Sn+ko8LcoY;La*2z@WTU~kk6S;HS1_6(w%OkJX~V&mf*gU&uktP|L&S#74>agcG> zC+9yK4j%up<6!>>$7cEZr^hEK6^r{x)n9$E{^Mgd`-A>ce?;tc&tB*K$96ycgZWF> zWwGK~mCZMg`W6HRCIxQLPE&2VI78N@H+S~Jj0-({4LW^I3Tlztjc&|X#n)7(bE9{& zddAnq6FB%;RvVWvPxa_td?@$T4w1ub_1*h(Ka~I2%~sz(Kc(unXRF8Cx%CIcHYPl& zpM2oK`_KF){}vv!f0RDMY)({i;ao}aWs-NBEyEf=Z~l|q%y&tg_aEo~D-WJ01iibf z*BZ4Vc&_xpi1Kwy59SGnnGPzS*!Q--UA$KBVrT#PvAYMHOy-BkNgqF(G4v*Qaxg=$NIwH}UJj7<5P*_Nhpw+RJU z>s@O;y*tJ8LXY#75IL4zSISmCJ@~Fdhw1l?k10+z52y4`T@+!kTZMnoqBXo?s$Gj- zwn$B>idZ)1VUNCb@YLM3pSW(Yybi6jN<8#n{>R!CC)_@6Rh8W8ET|o9>3QL=OzD}7 zZ)^MOkDq1qJjnLHDbD1_^gr6o^}6x<8<}6Sc5?1j7V$m0(V#-E^z_NBeTFam?kDe= zyLX~Qm&y08)YClwbdz{v>e4^F|H;}MFLL1gsn~8K)*_jE=jI%&e_+qG*xvK=C5fd* zTUQ;d7wfJ#GEXSA-g9#AT1(!#$P>@@7v5a?F`oCI^#A(159&SM>CBmt9Hgd^+o5_m zR;wsrhBDjbsg)b@x0vvk9e=dd@udC#D?j4d{zuC!J}NXd=tssM`R4e<{$uN<{>0lQ ze#rh~wXED)bnQW9J)@X6MGHkvMedONvg&m14x54{r(b_ss%x__$~b1$5qjKrvtS)$_WL|$fk zO0QU$eeTafrCz#6~ke+z6HCt@?+Pxod$qOqdhq@&E~@Tg5kT%X0p{;Er@G4EE#sSy23XW3}kE zRR4{0ZG_Kg=FRY!Um2opzxH%y+nxO5mx?kLx+E_-f2{Z3^|j~ZO5JB4$a(I<{anPp z*t&kjwA%+~7j>2XGWYaXDq&sox=md@Lg(@Gn8f{RVw(k-((b!Wsad;Ca{E!{;H4XK z%q9m;k^ji6=JxD)-A1Y2Nj-L^D{sHH_;mUD(e%B{Q~i_fa68hl$#u4qH^StQr@=3Zt>@~+)X>-*4lj7;b>Zzn*JHDfaLfY>Cfxf zCjL$9@mm_lka|Tw@b;Q{Kf~q)>l&@66`+OF^mMX5+k#p|2TUee0O~(`@-Vu1u!q52k*0nrFX#Yudke z(@X=GPYNl{G2gUx=83A$r8-Strl5O;_{qhO0|fT+Lrz*SEb+%{IfJ?emvs-7(MVZQirH6&gPj zT&orR)Np;XI`fKk0rNAsJ##9`AFFJ9A#=-F)#b@jhQ0lHD$Bk{`_=I(e_{V3eTKJjza(z;KfZqT!2`$r3radZd9{1ypPz3scb<&W-dLZ$EHuNuS97Hfc@$nk6$scqOwk>~t4?&d_{&*k|S8$=@!ob$R8T zlJr$IXy=vbC!QXAq5jgWr^CeEL!I~Ut4)rxb_t%{GVRj(3nDc>>%X$tG*5|tJvGm5 zE&t=m%=;2&-Hem^z`EyYs;F~_fyd^);D-wfi`F&tJ#Q}7Oy#AXYSB}iG&9_dsW%zYh?@a$?QgtEzweE)hg^DH-4&jwAy{m*yrFkMXTl4UdO%D z{=jO(f8Vov`DKp-!4IN8y;Cm9TPt>f?WN86<(WYTtaCpq=w@(Nq^?-;)=FMp;^(xS z?H>eM=5QU3VPMo>P<3_p)SKIwRb0%jcr_)NFSyJv&VS)%39IfR?Ui%xb(V%7o^UeVY9b+&ukqh45LG1nVdzo>e(_1VpUUz$T;n}!fTs*xJ%*%gg2Ar(3yK-RpScs~`ffO{ z$8y1L>H0|q$(!?7gTvzc4{p;dxLNNSzjXbm-|Hf{U1YVD}ojiG^?uUY$O-(tR}X!ZZbL!MVMzV91c$}g(yl4kraY4KjJ=YIE< zqI!OT@)_3{Sbv8s_!Z(&&m2|hxVLFvr@p5ZujE_l7tvaVr>3*4^scRqX8qi(Rr;y^ zpuz3C_ZPljW+LOLKXG^Bj4-8ZS*ns(-!2Y)5w_LgwW{c&L+#V86(b((oqTTMht-8n z?|pxt(BRK-&)nKQXTtBwpB2;j8vhH<_^BaM+mOawzvZexwyG`5%r-&4{$^xydiae}+CL@ekgf z^J)0euGRUEG=1O1yQ!&PROf>G#hRZ&|qLZawjAcEank z8y4nIxOXY;b^RPJ=BR&S>P}4mt~qAqI){k}=jU2F6w7YA{ju}&Lyoo|bEpI{Ys{O1h4>vyKn^oWXtuW#@=Z*K8 zvlzFCGh}!(Y>aZq;#Ii9w&YEdx?Z#Xx~7%iE)+lb!n5G3j^Fm@2i_j%xIF)Wy*=xK zdNzgsGXMT8{gfg9;8t7l=i@V`PtayMTYO{f^k8%52kg-^wFTYFV7+UuWHVi_`j|Pvn-Ke!{WwiT0TQYo*SO z8}`ODDOI&31+2fOZvSh7zlz#D#xM4^K7}oQi)1=7cS_bmWYGj|E*G!)?ahCqzFdDKu-|O*>TL?o zPZZ8{|L?l4inpHgXQ`Icp@_ycg^asbOuV~gUhS6VzgNy$napwt{NnlLZ_b6OHm&O( ztt`>~C4FK4{NFM$Pp|V?6_+7om|woW7)hd*E6&pc_-KPp1ZEW6jR@y@cB)^1kZH0 z=Aa)o|Kv|qPmNjSaY9D$w#W464@7)!D=y(Ml0W`U^moQLgV>w%{`;wiPd#O5?(*I$ ztV*N#h~}him3o;r{m(w#JjK&?xKZ_hXXk4%q1bsH!M9^LWm{M5Dw%L^?|iYT&*Duz z!rvd?_VfCR{-#TRvIUEOpVT;8U(av%ZQ?Ua2f6PVJS&W5oM*nxYSP@j;kT1Tp`h-u z8!JSvMh5*j5&Nvp_)w7Cnqc=wxf>c=QZJTBT)Q8{rgJto{@u>0%cg{{X!^Q~Sy^B4 z$1~-N-{j@CS4#feF=<(`sM&hQom*SPK3rO}XsO2Ihe`{ccL=TEkf?GM)%BbBqK%Wg zzHw!B@I2EKrA}SDlhs!=xX(2?dwz1G+3~902-W3F!=l=Zj@nn5epspNHHE{mo#jJ` z%LRrY_7o}q>AN^}K3BSRrHWR~{CtXW^6Z7l0qYM7bf*_N_8r}KIohV1JGeVLSn~Sv ztP6Km&t-Xi`o*K-62|#AWlkJDUi^XQ=bWR(K^>_#c$&Q{ekI1l?7!1qTvVjj{EtW7 z-omnUru?@`tG62Bo5nsuBgkJztOMc>Ah#Yb2VWD@YWj5l?#3_Y1MdB21dReucDN(8IUtI$5xOcO?6$WI${Ym) z0xW6lcA8wMBRYAnlhoulw*|Nm)=Z05O1!DE5RKF_3>9&Q2)bwBf9lHN9X;Yy{+-=7&Um7bW~cwT9;_j3cLrVEoB&+AQ& gc_1)(_j4(xWtS&kf1buvb#=1+3vV{x>!5H30K|+;4gdfE delta 8722 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 Date: Mon, 5 Dec 2022 15:45:07 +0100 Subject: [PATCH 082/545] Fix a typo in excuse message subject (#2071) --- .../wulkanowy/ui/modules/message/send/SendMessagePresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index 5ab8f8fc9..e776e9941 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -55,7 +55,7 @@ class SendMessagePresenter @Inject constructor( view.showMessageBackupDialog() } reason?.let { - setSubject("Usprawiedliwenie") + setSubject("Usprawiedliwienie") setContent(it) } message?.let { From c34c63c128d50e6ef2ffecbf234c125e2f042289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 20 Dec 2022 16:06:55 +0100 Subject: [PATCH 083/545] Add support for new ADFS light instances (#2084) * Update known symbols * Update resman host details * Add adfslight from tomaszowmazowiecki * Bump sdk to 1.8.2-SNAPSHOT * Add migration 54 with tests * Close db in migration tests * Run tests workflow on every pull request --- .github/workflows/test.yml | 1 - app/build.gradle | 2 +- .../54.json | 2439 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../data/db/migrations/Migration54.kt | 26 + app/src/main/res/values/api_hosts.xml | 9 +- app/src/main/res/values/api_symbols.xml | 666 ++++- .../db/migrations/AbstractMigrationTest.kt | 7 +- .../data/db/migrations/Migration12Test.kt | 9 +- .../data/db/migrations/Migration13Test.kt | 5 + .../data/db/migrations/Migration27Test.kt | 12 +- .../data/db/migrations/Migration35Test.kt | 4 +- .../data/db/migrations/Migration54Test.kt | 130 + build.gradle | 1 + 14 files changed, 3150 insertions(+), 164 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3def08953..13875078a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: branches: [ master, develop ] tags: [ '*' ] pull_request: - branches: [ master, develop ] jobs: diff --git a/app/build.gradle b/app/build.gradle index fdb844aca..b0865896e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.8.1" + implementation "io.github.wulkanowy:sdk:1.8.2-SNAPSHOT" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json new file mode 100644 index 000000000..7b41672b9 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/54.json @@ -0,0 +1,2439 @@ +{ + "formatVersion": 1, + "database": { + "version": 54, + "identityHash": "1dc96a366125ec9f8567da87cdc9c863", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_global_key` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "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": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "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": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dc96a366125ec9f8567da87cdc9c863')" + ] + } +} \ 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 792611a81..cfb53485f 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 @@ -56,7 +56,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 53 + const val VERSION_SCHEMA = 54 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -107,6 +107,7 @@ abstract class AppDatabase : RoomDatabase() { Migration50(), Migration51(), Migration53(), + Migration54(), ) fun newInstance( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt new file mode 100644 index 000000000..678bd32f2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration54 : Migration(53, 54) { + + override fun migrate(database: SupportSQLiteDatabase) { + migrateResman(database) + removeTomaszowMazowieckiStudents(database) + } + + private fun migrateResman(database: SupportSQLiteDatabase) { + database.execSQL(""" + UPDATE Students SET + scrapper_base_url = 'https://vulcan.net.pl', + login_type = 'ADFSLightScoped', + symbol = 'rzeszowprojekt' + WHERE scrapper_base_url = 'https://resman.pl' + """.trimIndent()) + } + + private fun removeTomaszowMazowieckiStudents(database: SupportSQLiteDatabase) { + database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'") + } +} diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 8413d68e4..a2a08db67 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -6,8 +6,9 @@ Gdańska Platforma Edukacyjna Lubelski Portal Oświatowy EduNet Miasta Tarnowa - ResMan Rzeszów Platforma Edukacyjna Koszalina + Gmina-miasto Tomaszów Mazowiecki - System zarządzania oświatą + ResMan Rzeszów Rawa Mazowiecka - Platforma vEdukacja Zduńska Wola - e-Urząd Sieradz - Portal oświatowy @@ -27,7 +28,6 @@ https://edu.gdansk.pl https://edu.lublin.eu https://umt.tarnow.pl - https://resman.pl https://eduportal.koszalin.pl https://vulcan.net.pl/?login https://vulcan.net.pl/?login @@ -40,6 +40,8 @@ https://vulcan.net.pl/?login https://vulcan.net.pl/?login https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login http://fakelog.cf/?email @@ -48,8 +50,9 @@ gdansk lublin tarnow - rzeszow koszalin + tomaszowmazowieckiprojekt + rzeszowprojekt rawamazowiecka zdunskawola sieradz diff --git a/app/src/main/res/values/api_symbols.xml b/app/src/main/res/values/api_symbols.xml index 6f9b1739d..4b61db48d 100644 --- a/app/src/main/res/values/api_symbols.xml +++ b/app/src/main/res/values/api_symbols.xml @@ -4,13 +4,11 @@ Andrychów Augustów Baranów Sandomierski - Bartoszyce Będzin Bełchatów Bełżyce Biała Podlaska Biała Rawska - Białogard Biały Bór Białystok Biecz @@ -18,11 +16,9 @@ Bielsko-Biała Bierawa Bierutów - Biskupice Blachownia Błaszki Błonie - Bochnia Bogatynia Boguchwała Boguty-Pianki @@ -45,6 +41,7 @@ Chełmża Chocianów Chodzież + Chojnice Chojnów Chorzów Ciechanów @@ -58,7 +55,9 @@ Dąbrowa Górnicza Dąbrowa Tarnowska Dębica + Dębno Dobrzeń Wielki + Dobrzeń Wielki 2 Dobrzyń Nad Wisłą Dolnośląskie Duszniki-Zdrój @@ -76,6 +75,9 @@ Głogów Małopolski Głowno Głubczyce + Głubczyce 2 + Głuchołazy + Gmina Abramów Gmina Adamówka Gmina Aleksandrów Kujawski Gmina Aleksandrów Łódzki @@ -87,14 +89,17 @@ Gmina Bądkowo Gmina Bałtów Gmina Baranów - Gmina Barciany Gmina Barcin + Gmina Barczewo Gmina Baruchowo + Gmina Batorz Gmina Będzino Gmina Bełchatów + Gmina Besko Gmina Białaczów Gmina Białe Błota - Gmina Bielsk Podlaski + Gmina Białopole + Gmina Bielsk Gmina Bircza Gmina Błażowa Gmina Błędów @@ -104,7 +109,6 @@ Gmina Bobrowniki Gmina Bodzentyn Gmina Bogoria - Gmina Bojanów Gmina Bojanowo Gmina Bojszowy Gmina Bolesławiec @@ -113,10 +117,14 @@ Gmina Borów Gmina Borowa Gmina Borzęcin + Gmina Borzytuchom + Gmina Bralin Gmina Branice Gmina Braniewo + Gmina Brańszczyk Gmina Brąszewice Gmina Brenna + Gmina Brok Gmina Brzeg Dolny Gmina Brzeziny Gmina Brzeźnio @@ -125,13 +133,14 @@ Gmina Brzuze Gmina Brzyska Gmina Buczek - Gmina Buczkowice Gmina Budzów Gmina Budzyń + Gmina Bukowina Tatrzańska Gmina Bukowsko Gmina Byczyna Gmina Bystra-Sidzina - Gmina Bytoń + Gmina Cegłów + Gmina Cekcyn Gmina Ceków-Kolonia Gmina Celestynów Gmina Cewice @@ -139,16 +148,25 @@ Gmina Chełm Gmina Chełmiec Gmina Chełmno + Gmina Chłopice Gmina Chmielnik + Gmina Chociwel Gmina Chocz Gmina Chodel + Gmina Chodów Gmina Chojnice Gmina Chojnów + Gmina Chotcza + Gmina Chrząstowice Gmina Chrzypsko Wielkie Gmina Chybie Gmina Ciasna + Gmina Ciechanów Gmina Ciechocin + Gmina Cielądz + Gmina Cieszanów Gmina Ciężkowice + Gmina Cisek Gmina Cisna Gmina Cmolas Gmina Cyców @@ -156,19 +174,26 @@ Gmina Czarna Gmina Czarnków Gmina Czarny Dunajec + Gmina Czastary Gmina Czechowice-Dziedzice Gmina Czernichów Gmina Czerniejewo + Gmina Czerniewice + Gmina Czernikowo Gmina Czerwionka-Leszczyny - Gmina Czerwonka + Gmina Czerwonak Gmina Człuchów Gmina Czosnów + Gmina Dąbrowa Zielona + Gmina Dąbrowice Gmina Damasławek Gmina Damnica Gmina Darłowo Gmina Dębe Wielkie Gmina Dębica Gmina Dębno + Gmina Dębowa Kłoda + Gmina Debrzno Gmina Dłutów Gmina Dobczyce Gmina Dobra @@ -176,19 +201,31 @@ Gmina Dobrodzień Gmina Dobroń Gmina Dobrzany + Gmina Dobrzyca Gmina Dobrzyniewo Duże + Gmina Dolsk Gmina Dominowo Gmina Dorohusk + Gmina Doruchów + Gmina Dragacz + Gmina Drawsko + Gmina Drużbice Gmina Drzewica Gmina Dubiecko + Gmina Dubienka Gmina Dukla Gmina Dwikozy + Gmina Dydnia Gmina Dynów Gmina Dziadowa Kłoda Gmina Działoszyce + Gmina Dziemiany Gmina Dzierżoniów + Gmina Dzwola Gmina Elbląg - Gmina Fajsławice + Gmina Ełk + Gmina Fredropol + Gmina Garbatka-Letnisko Gmina Garbów Gmina Garwolin Gmina Gąsawa @@ -198,29 +235,37 @@ Gmina Gdów Gmina Gielniów Gmina Gierałtowice - Gmina Glinojeck Gmina Głogów + Gmina Głogówek Gmina Głuchów + Gmina Głusk Gmina Głuszyca Gmina Gniew + Gmina Gniewino Gmina Gniewoszów Gmina Gniezno Gmina Goczałkowice-Zdrój Gmina Godkowo Gmina Godów Gmina Godziesze Wielkie + Gmina Godziszów Gmina Gołańcz + Gmina Gołcza Gmina Goleszów Gmina Golina Gmina Golub-Dobrzyń + Gmina Gołuchów + Gmina Gomunice Gmina Goraj Gmina Gorlice Gmina Górno + Gmina Górzyca Gmina Gościeradów Gmina Gostyń Gmina Gostynin Gmina Goszczyn Gmina Gózd + Gmina Grabica Gmina Grabów Gmina Grabowiec Gmina Grabów Nad Pilicą @@ -239,45 +284,52 @@ Gmina Grudziądz Gmina Gruta Gmina Grybów + Gmina Gryfice + Gmina Grzmiąca Gmina Haczów Gmina Halinów Gmina Hańsk Gmina Harasiuki Gmina Hażlach Gmina Herby + Gmina Horodło Gmina Hrubieszów Gmina Huszlew Gmina Hyżne Gmina Imielno Gmina Inowrocław + Gmina Irządze Gmina Istebna + Gmina Iwanowice Gmina Iwierzyce Gmina Iwonicz-Zdrój Gmina Izabelin Gmina Izbica - Gmina Jadów + Gmina Izbicko + Gmina Jabłoń Gmina Jaktorów + Gmina Jakubów Gmina Janikowo + Gmina Janów Gmina Janowiec Gmina Janów Podlaski - Gmina Jaraczewo + Gmina Jarczów Gmina Jarocin Gmina Jasienica Rosielna + Gmina Jaśliska Gmina Jasło Gmina Jastków Gmina Jastrowie Gmina Jastrząb Gmina Jedlicze - Gmina Jedlińsk - Gmina Jedlnia-Letnisko Gmina Jejkowice Gmina Jemielnica - Gmina Jemielno Gmina Jerzmanowa Gmina Jeżewo Gmina Jeziora Wielkie Gmina Jeziorzany Gmina Jeżowe + Gmina Joniec Gmina Jordanów Gmina Józefów Gmina Józefów Nad Wisłą @@ -285,14 +337,18 @@ Gmina Kąkolewnica Gmina Kamień Gmina Kamienica - Gmina Kamieniec + Gmina Kamiennik Gmina Kamionka Gmina Karczmiska Gmina Kargowa + Gmina Karlino + Gmina Karniewo Gmina Kawęczyn Gmina Kazimierz Biskupi Gmina Kępice + Gmina Kęsowo Gmina Kiełczygłów + Gmina Kietrz Gmina Kikół Gmina Kiszkowo Gmina Kleczew @@ -307,22 +363,30 @@ Gmina Klucze Gmina Kluczewsko Gmina Kobiele Wielkie + Gmina Kobylanka Gmina Kochanowice Gmina Kock Gmina Kodrąb Gmina Kołaczyce Gmina Kołbaskowo + Gmina Kołbiel Gmina Kołczygłowy + Gmina Kołobrzeg Gmina Koluszki Gmina Komańcza + Gmina Komarówka Podlaska Gmina Komorniki Gmina Komprachcice Gmina Konarzyny Gmina Kondratowice + Gmina Koneck Gmina Koniusza Gmina Konopiska Gmina Końskowola + Gmina Konstantynów Gmina Koprzywnica + Gmina Korfantów + Gmina Kórnik Gmina Korsze Gmina Korycin Gmina Korzenna @@ -332,10 +396,14 @@ Gmina Kościerzyna Gmina Kosów Lacki Gmina Kostrzyn - Gmina Koszyce + Gmina Koszęcin Gmina Kotla Gmina Kotuń + Gmina Kowiesy + Gmina Koziegłowy Gmina Kozłów + Gmina Kramsk + Gmina Kraśniczyn Gmina Kraśnik Gmina Krasnobród Gmina Krasnystaw @@ -345,12 +413,16 @@ Gmina Krośnice Gmina Krupski Młyn Gmina Kruszwica + Gmina Krynice Gmina Krynki Gmina Krzanowice Gmina Krzemieniewo + Gmina Krzeszów Gmina Krzymów + Gmina Krzywcza Gmina Krzywiń Gmina Krzyżanowice + Gmina Ksawerów Gmina Książ Wielki Gmina Kunice Gmina Kunów @@ -360,15 +432,19 @@ Gmina Kwilcz Gmina Łabowa Gmina Łabunie + Gmina Łączna + Gmina Lądek Gmina Łambinowice Gmina Lanckorona + Gmina Łańcut + Gmina Łapanów Gmina Łapsze Niżne Gmina Łasin Gmina Łaskarzew Gmina Lasowice Wielkie Gmina Łaszczów - Gmina Laszki Gmina Latowicz + Gmina Łaziska Gmina Łazy Gmina Łęczyca Gmina Łęczyce @@ -378,11 +454,14 @@ Gmina Lelów Gmina Leśna Gmina Leśna Podlaska + Gmina Leśniowice Gmina Lesznowola Gmina Leżajsk Gmina Lichnowy Gmina Limanowa Gmina Linia + Gmina Liniewo + Gmina Lipiany Gmina Lipinki Gmina Lipnik Gmina Lipowa @@ -390,43 +469,51 @@ Gmina Liszki Gmina Liw Gmina Łobez + Gmina Łochów Gmina Łodygowice Gmina Łomazy + Gmina Łomianki + Gmina Łoniów Gmina Łopiennik Górny Gmina Łopuszno + Gmina Łosice Gmina Lubań Gmina Lubartów Gmina Lubasz + Gmina Lubawka Gmina Lubenia Gmina Łubianka Gmina Lubicz + Gmina Lubień Gmina Lubiewo Gmina Lubin Gmina Łubniany Gmina Lubochnia - Gmina Lubomia Gmina Luboń + Gmina Lubsza + Gmina Lubycza Królewska Gmina Łuków Gmina Łukowica Gmina Lutowiska + Gmina Lututów Gmina Luzino Gmina Łużna Gmina Łysomice + Gmina Maciejowice Gmina Magnuszew + Gmina Majdan Królewski Gmina Maków Podhalański - Gmina Mała Wieś Gmina Malbork Gmina Małdyty Gmina Małkinia Górna Gmina Marcinowice Gmina Margonin Gmina Marianowo - Gmina Markusy - Gmina Masłów + Gmina Markuszów + Gmina Męcinka Gmina Medyka Gmina Mełgiew Gmina Michałów - Gmina Michałowo Gmina Miedziana Góra Gmina Miedźna Gmina Miedźno @@ -435,7 +522,10 @@ Gmina Międzyrzec Podlaski Gmina Międzyzdroje Gmina Miejsce Piastowe + Gmina Miękinia Gmina Mielec + Gmina Mielno + Gmina Mieszkowice Gmina Milanów Gmina Milejów Gmina Milicz @@ -444,21 +534,26 @@ Gmina Miłosław Gmina Milówka Gmina Mińsk Mazowiecki + Gmina Mirów Gmina Mirsk Gmina Młynary + Gmina Modliborzyce Gmina Mogielnica Gmina Mogilany + Gmina Mogilno + Gmina Morawica Gmina Mordy Gmina Moryń Gmina Mrocza Gmina Mrozy + Gmina Mściwojów + Gmina Mstów Gmina Mszana Gmina Mszana Dolna Gmina Murów Gmina Mycielin - Gmina Mysłakowice + Gmina Mykanów Gmina Myślibórz - Gmina Nadarzyn Gmina Namysłów Gmina Nasielsk Gmina Nawojowa @@ -469,23 +564,25 @@ Gmina Niedrzwica Duża Gmina Niedźwiada Gmina Niedźwiedź - Gmina Niegosławice - Gmina Niwiska + Gmina Nowa Karczma Gmina Nowa Ruda Gmina Nowa Wieś Lęborska + Gmina Nowe Gmina Nowe Miasto Gmina Nowe Miasto Nad Wartą - Gmina Nowogród Bobrzański + Gmina Nowogród Gmina Nowosolna Gmina Nowy Kawęczyn + Gmina Nowy Korczyn Gmina Nowy Staw Gmina Nowy Targ Gmina Nowy Tomyśl + Gmina Nozdrzec Gmina Nur Gmina Obrazów Gmina Ochotnica Dolna Gmina Ogrodzieniec - Gmina Olecko + Gmina Olszanica Gmina Olsztynek Gmina Olszyna Gmina Opatowiec @@ -495,11 +592,15 @@ Gmina Osiek Jasielski Gmina Osiek Mały Gmina Osielsko + Gmina Osina + Gmina Osjaków + Gmina Ostroróg Gmina Ostrów Gmina Ostrówek Gmina Ostrów Lubelski Gmina Ostrów Mazowiecka Gmina Ostrów Wielkopolski + Gmina Otmuchów Gmina Otyń Gmina Ożarów Gmina Ożarowice @@ -507,27 +608,32 @@ Gmina Ozorków Gmina Pabianice Gmina Pacanów + Gmina Pacyna Gmina Paczków Gmina Padew Narodowa - Gmina Pajęczno Gmina Pakosław Gmina Pakosławice Gmina Pałecznica Gmina Panki Gmina Parchowo Gmina Parczew - Gmina Pawłosiów + Gmina Pasłęk + Gmina Pątnów Gmina Pawłowice Gmina Pawłowiczki + Gmina Pawonków Gmina Pęcław Gmina Pelplin + Gmina Pępowo Gmina Piaski Gmina Piątnica - Gmina Piecki Gmina Piekoszów + Gmina Pieniężno Gmina Pilchowice + Gmina Pińczów Gmina Pionki - Gmina Piotrków Trybunalski + Gmina Płaska + Gmina Platerówka Gmina Pleśna Gmina Pleszew Gmina Płońsk @@ -535,6 +641,7 @@ Gmina Poczesna Gmina Podedwórze Gmina Podegrodzie + Gmina Podgórzyn Gmina Pokój Gmina Połajewo Gmina Połaniec @@ -543,16 +650,18 @@ Gmina Police Gmina Polkowice Gmina Pomiechówek + Gmina Poniatowa Gmina Popielów Gmina Popów - Gmina Poraj Gmina Potęgowo + Gmina Potok Wielki Gmina Praszka - Gmina Prażmów Gmina Prochowice Gmina Promna Gmina Prószków + Gmina Prusice Gmina Pruszcz Gdański + Gmina Przechlewo Gmina Przecław Gmina Przedecz Gmina Przemęt @@ -562,7 +671,9 @@ Gmina Przodkowo Gmina Przykona Gmina Przyłęk + Gmina Przyrów Gmina Przystajń + Gmina Przytoczna Gmina Puchaczów Gmina Puck Gmina Puławy @@ -570,10 +681,10 @@ Gmina Puszcza Mariańska Gmina Pysznica Gmina Pyzdry + Gmina Raba Wyżna Gmina Rachanie Gmina Raciechowice - Gmina Racławice - Gmina Radecznica + Gmina Radgoszcz Gmina Radków Gmina Radłów Gmina Radomin @@ -581,16 +692,29 @@ Gmina Radomyśl Nad Sanem Gmina Radoszyce Gmina Radwanice + Gmina Radymno + Gmina Radziejów Gmina Radziłów + Gmina Rajgród + Gmina Raków + Gmina Rakszawa Gmina Rawa Mazowiecka + Gmina Regnów Gmina Reńska Wieś + Gmina Rogóźno + Gmina Rokitno + Gmina Ropa Gmina Rossosz Gmina Rozprza Gmina Ruciane-Nida Gmina Ruda-Huta Gmina Rudna Gmina Rudniki + Gmina Rudnik Nad Sanem + Gmina Rudziniec Gmina Rusiec + Gmina Rusinów + Gmina Rybczewice Gmina Rychliki Gmina Rychtal Gmina Ryczywół @@ -598,32 +722,39 @@ Gmina Rypin Gmina Rytro Gmina Rytwiany - Gmina Rząśnia Gmina Rzeczyca Gmina Rzepiennik Strzyżewski Gmina Rzepin + Gmina Rzezawa Gmina Rzgów Gmina Sadki Gmina Sadowne Gmina Samborzec Gmina Sanok + Gmina Sawin Gmina Ścinawa Gmina Sędziejowice + Gmina Sejny + Gmina Sękowa Gmina Sępopol Gmina Serokomla Gmina Sianów Gmina Sicienko Gmina Sieciechów Gmina Siedlce + Gmina Siedliszcze Gmina Siemiatycze + Gmina Siemień Gmina Siemyśl Gmina Siennica Gmina Siennica Różana Gmina Sienno Gmina Siepraw Gmina Sieradz + Gmina Sieraków Gmina Sierakowice Gmina Siewierz + Gmina Sitkówka-Nowiny Gmina Sitno Gmina Skarżysko Kościelne Gmina Skępe @@ -631,16 +762,22 @@ Gmina Skoczów Gmina Skoki Gmina Skołyszyn + Gmina Skrwilno Gmina Skrzyszów Gmina Skulsk + Gmina Skwierzyna Gmina Sława + Gmina Śliwice Gmina Słopnice + Gmina Słubice Gmina Słupca Gmina Słupia + Gmina Słupia (Konecka) + Gmina Śmigiel Gmina Sobienie-Jeziory + Gmina Sobolew Gmina Sobótka Gmina Sokółka - Gmina Sokoły Gmina Solina Gmina Sośnicowice Gmina Sośnie @@ -655,11 +792,16 @@ Gmina Stare Miasto Gmina Stare Pole Gmina Starogard Gdański + Gmina Stary Brus + Gmina Stary Dzierzgoń + Gmina Stary Targ Gmina Stawiszyn + Gmina Stepnica Gmina Stoczek Łukowski Gmina Stopnica Gmina Strawczyn Gmina Stryków + Gmina Stryszawa Gmina Stryszów Gmina Strzałkowo Gmina Strzelce Opolskie @@ -668,38 +810,43 @@ Gmina Strzyżewice Gmina Stupsk Gmina Subkowy + Gmina Suchań Gmina Suchedniów Gmina Suchożebry Gmina Suchy Las Gmina Sulechów Gmina Sulęcin + Gmina Sulejów Gmina Sulików Gmina Sulmierzyce Gmina Sułów Gmina Susiec - Gmina Świerklaniec + Gmina Świercze + Gmina Świerczów + Gmina Świerklany Gmina Świerzawa Gmina Świeszyno Gmina Świlcza Gmina Szadek Gmina Szaflary Gmina Szastarka + Gmina Szczawin Kościelny Gmina Szczebrzeszyn Gmina Szczekociny Gmina Szczerców - Gmina Szczutowo Gmina Szczytna Gmina Szczytniki - Gmina Szemud + Gmina Szczytno Gmina Szerzyny Gmina Szlichtyngowa + Gmina Szreńsk + Gmina Szudziałowo Gmina Szydłów Gmina Tarłów Gmina Tarnów Gmina Tarnowiec Gmina Tarnów Opolski Gmina Teresin - Gmina Terespol Gmina Tereszpol Gmina Tłuchowo Gmina Tłuszcz @@ -709,12 +856,15 @@ Gmina Toszek Gmina Trąbki Wielkie Gmina Trzebiatów + Gmina Trzebielino Gmina Trzebinia - Gmina Trzeszczany Gmina Trzyciąż + Gmina Trzydnik Duży Gmina Tuchów + Gmina Tułowice Gmina Turośń Kościelna Gmina Tuszów Narodowy + Gmina Tworóg Gmina Tyczyn Gmina Tymbark Gmina Tyrawa Wołoska @@ -723,15 +873,21 @@ Gmina Ulan-Majorat Gmina Ulanów Gmina Ułęż + Gmina Ulhówek Gmina Urszulin Gmina Urzędów + Gmina Uście Gorlickie Gmina Uścimów Gmina Wąchock + Gmina Wądroże Wielkie Gmina Wągrowiec + Gmina Walce Gmina Wąpielsk Gmina Wasilków + Gmina Wąsosz Gmina Wąwolnica Gmina Wejherowo + Gmina Werbkowice Gmina Wiązów Gmina Wiązowna Gmina Wicko @@ -739,16 +895,21 @@ Gmina Wielbark Gmina Wieleń Gmina Wielgie + Gmina Wielgomłyny Gmina Wieliszew Gmina Wielka Nieszawka Gmina Wieniawa Gmina Wieprz Gmina Wieruszów + Gmina Wierzbinek Gmina Wierzbno + Gmina Wierzchlas Gmina Wierzchosławice Gmina Wietrzychowice Gmina Wijewo + Gmina Wilczyce Gmina Wilczyn + Gmina Wilkołaz Gmina Wilków Gmina Wilkowice Gmina Winnica @@ -759,12 +920,14 @@ Gmina Witkowo Gmina Władysławów Gmina Wleń + Gmina Włocławek Gmina Włodawa Gmina Włoszczowa Gmina Wodzierady Gmina Wodzisław Gmina Wojcieszków Gmina Wojnicz + Gmina Wojsławice Gmina Wola Krzysztoporska Gmina Wolanów Gmina Wolbrom @@ -775,15 +938,18 @@ Gmina Wręczyca Wielka Gmina Wronki Gmina Wyrzysk - Gmina Zabierzów + Gmina Wysokie Gmina Żabno Gmina Żagań - Gmina Zagórz + Gmina Zagórów Gmina Zaklików Gmina Zakroczym Gmina Zakrzówek + Gmina Zalesie Gmina Zaleszany + Gmina Załuski Gmina Zamość + Gmina Żarnów Gmina Żarnowiec Gmina Żarów Gmina Zarszyn @@ -795,20 +961,26 @@ Gmina Zbójno Gmina Zbrosławice Gmina Zduńska Wola - Gmina Zduny Gmina Zdzieszowice + Gmina Zębowice Gmina Zebrzydowice + Gmina Żegocina Gmina Żelazków + Gmina Zembrzyce Gmina Zgierz Gmina Zgorzelec Gmina Ziębice Gmina Zielonki Gmina Zławieś Wielka + Gmina Złota + Gmina Złotniki Kujawskie Gmina Żmudź Gmina Żnin Gmina Żółkiewka Gmina Żołynia Gmina Żukowice + Gmina Żurawica + Gmina Żyraków Gmina Żyrzyn Gmina Żytno Gniezno @@ -817,19 +989,19 @@ Góra Góra Kalwaria Gorlice + Górzno Gorzów Śląski Gorzów Wielkopolski Gostynin Grajewo Grodzisk Mazowiecki - Gronowo Elbląskie Grudziądz + Grybów Gryfino Gryfów Śląski Hel Hrubieszów Inowrocław - Iwanowice Izbica Kujawska Jabłonowo Pomorskie Janowiec Wielkopolski @@ -839,7 +1011,6 @@ Jasło Jastrzębie-Zdrój Jawor - Jaworzno Jedlina-Zdrój Jelcz-Laskowice Jelenia Góra @@ -860,9 +1031,9 @@ Kępno Kętrzyn Kielce - Kiełczygłów Kłodawa Kłodzko + Kluczbork Knurów Kobyłka Koło @@ -892,11 +1063,13 @@ Krzeszowice Krzyż Wielkopolski Książ Wielkopolski - Kudowa-Zdrój Kujawsko-Pomorskie + Kutno Kuźnia Raciborska + Kwidzyn Łabiszyn Lądek-Zdrój + Łańcut Łapy Łask Łaskarzew @@ -908,10 +1081,10 @@ Legnica Leszno Lewin Brzeski + Lewin Brzeski 2 Leżajsk Limanowa Lipno - Lipsko Łódź Łódzkie Łowicz @@ -928,11 +1101,8 @@ Lwówek Śląski Malbork Małopolskie - Marciszów Marki - Masłowice Mazowieckie - Miastko Michałowice Miechów Międzyrzec Podlaski @@ -940,6 +1110,7 @@ Mielec Milanówek Mińsk Mazowiecki + Mniszków Mosina Mrągowo Mrągowski @@ -949,18 +1120,17 @@ Mysłowice Myszków Nakło Nad Notecią - Nasielsk Niemodlin Niepołomice Nisko Nowa Dęba Nowa Sarzyna + Nowa Sól Nowe Miasteczko Nowe Skalmierzyce Nowogard Nowogród Bobrzański Nowogrodziec - Nowosolna Nowy Dwór Mazowiecki Nowy Sącz Nowy Targ @@ -974,6 +1144,8 @@ Opoczno Opole Opole Lubelskie + Opolskie + Orzesze Osieczna Osiecznica Ostróda @@ -991,13 +1163,16 @@ Piekary Śląskie Pieńsk Piła + Pilzno Piotrków Trybunalski Pisz Płock Płońsk Pniewy + Pobiedziska Podkarpackie Podkowa Leśna + Podlaskie Połczyn-Zdrój Pomorskie Poniec @@ -1006,7 +1181,7 @@ Powiat augustowski Powiat będziński Powiat bełchatowski - Powiat białobrzeski + Powiat białostocki Powiat bialski Powiat bielski Powiat bieszczadzki @@ -1032,6 +1207,7 @@ Powiat człuchowski Powiat dąbrowski Powiat dębicki + Powiat drawski Powiat działdowski Powiat dzierżoniowski Powiat elbląski @@ -1046,10 +1222,11 @@ Powiat goleniowski Powiat golubsko-dobrzyński Powiat gorlicki + Powiat górowski Powiat gorzowski Powiat gostyński + Powiat grajewski Powiat grójecki - Powiat grudziądzki Powiat gryficki Powiat gryfiński Powiat hajnowski @@ -1064,6 +1241,7 @@ Powiat jędrzejowski Powiat jeleniogórski Powiat kaliski + Powiat kamiennogórski Powiat kamieński Powiat kartuski Powiat kazimierski @@ -1088,6 +1266,7 @@ Powiat krośnieński Powiat krotoszyński Powiat kutnowski + Powiat łańcucki Powiat łaski Powiat lęborski Powiat łęczycki @@ -1102,6 +1281,7 @@ Powiat lipski Powiat łobeski Powiat łódzki wschodni + Powiat łosicki Powiat łowicki Powiat lubaczowski Powiat lubański @@ -1122,9 +1302,11 @@ Powiat myślenicki Powiat myszkowski Powiat nakielski + Powiat namysłowski Powiat nidzicki Powiat niżański Powiat nowodworski + Powiat nowomiejski Powiat nowosądecki Powiat nowosolski Powiat nowotarski @@ -1160,7 +1342,6 @@ Powiat proszowicki Powiat prudnicki Powiat pruszkowski - Powiat przasnyski Powiat przemyski Powiat przeworski Powiat przysuski @@ -1183,6 +1364,7 @@ Powiat rzeszowski Powiat sandomierski Powiat sanocki + Powiat sejneński Powiat sępoleński Powiat siedlecki Powiat siemiatycki @@ -1210,6 +1392,7 @@ Powiat świdnicki Powiat świdwiński Powiat świebodziński + Powiat świecki Powiat szamotulski Powiat szczycieński Powiat sztumski @@ -1228,6 +1411,7 @@ Powiat wągrowiecki Powiat wałecki Powiat warszawski zachodni + Powiat węgorzewski Powiat węgrowski Powiat wejherowski Powiat wielicki @@ -1247,7 +1431,6 @@ Powiat wyszkowski Powiat ząbkowicki Powiat żagański - Powiat zambrowski Powiat zamojski Powiat żarski Powiat zawierciański @@ -1258,34 +1441,33 @@ Powiat złotoryjski Powiat złotowski Powiat żniński - Powiat żuromiński Powiat żyrardowski Powiat żywiecki Poznań - Prostki Proszowice Prudnik + Pruszcz Gdański Pruszków Przasnysz Przemyśl Przeworsk Przysucha Pszczyna - Pszów Puck Puławy + Pułtusk + Puszczykowo Pyskowice Rabka-Zdrój Raciąż Racibórz - Raciechowice Radom Radomsko + Radomyśl Wielki Radymno Radziejów Radzionków Radzyń Podlaski - Raków Rawa Mazowiecka Rawicz Reda @@ -1299,6 +1481,7 @@ Rymanów Rypin Rzeszów + Rzeszów projekt Sandomierz Sanok Sędziszów Małopolski @@ -1306,7 +1489,6 @@ Siedlce Siemianowice Śląskie Siemiatycze - Sieniawa Sieradz Skarżysko-Kamienna Skawina @@ -1325,17 +1507,15 @@ Środa Śląska Środa Wielkopolska Starachowice + Stargard Starogard Gdański Stary Sącz Staszów Stronie Śląskie - Strzegom Strzyżów - Suchy Las Sulejówek Sułkowice Sulmierzyce - Suwalski Swarzędz Świdnica Świdnik @@ -1358,8 +1538,10 @@ Terespol Tomaszów Lubelski Tomaszów Mazowiecki + Tomaszów Mazowiecki projekt Toruń Trzcianka + Trzcińsko-Zdrój Trzebnica Trzemeszno Tuliszków @@ -1372,7 +1554,7 @@ Ustrzyki Dolne Wadowice Wągrowiec - Wałbrzych + Wałcz Warmińsko-Mazurskie Warszawa Wąsosz @@ -1381,7 +1563,7 @@ Więcbork Wieliczka Wielkopolskie - Wizna + Wieluń Władysławowo Włocławek Włodawa @@ -1410,6 +1592,7 @@ Zielona Góra Zielonka Złotoryja + Złotów Żory Zwoleń Żyrardów @@ -1418,13 +1601,11 @@ andrychow augustow baranowsandomierski - bartoszyce bedzin belchatow belzyce bialapodlaska bialarawska - bialogard bialybor bialystok biecz @@ -1432,11 +1613,9 @@ bielskobiala bierawa bierutow - biskupice blachownia blaszki blonie - bochnia bogatynia boguchwala bogutypianki @@ -1459,6 +1638,7 @@ chelmza chocianow chodziez + chojnice chojnow chorzow ciechanow @@ -1472,7 +1652,9 @@ dabrowagornicza dabrowatarnowska debica + debno dobrzenwielki + dobrzenwielki2 dobrzynnadwisla dolnoslaskie dusznikizdroj @@ -1490,6 +1672,9 @@ glogowmalopolski glowno glubczyce + glubczyce2 + glucholazy + gminaabramow gminaadamowka gminaaleksandrowkujawski gminaaleksandrowlodzki @@ -1501,14 +1686,17 @@ gminabadkowo gminabaltow gminabaranow - gminabarciany gminabarcin + gminabarczewo gminabaruchowo + gminabatorz gminabedzino gminabelchatow + gminabesko gminabialaczow gminabialeblota - gminabielskpodlaski + gminabialopole + gminabielsk gminabircza gminablazowa gminabledow @@ -1518,7 +1706,6 @@ gminabobrowniki gminabodzentyn gminabogoria - gminabojanow gminabojanowo gminabojszowy gminaboleslawiec @@ -1527,10 +1714,14 @@ gminaborow gminaborowa gminaborzecin + gminaborzytuchom + gminabralin gminabranice gminabraniewo + gminabranszczyk gminabraszewice gminabrenna + gminabrok gminabrzegdolny gminabrzeziny gminabrzeznio @@ -1539,13 +1730,14 @@ gminabrzuze gminabrzyska gminabuczek - gminabuczkowice gminabudzow gminabudzyn + gminabukowinatatrzanska gminabukowsko gminabyczyna gminabystrasidzina - gminabyton + gminaceglow + gminacekcyn gminacekowkolonia gminacelestynow gminacewice @@ -1553,16 +1745,25 @@ gminachelm gminachelmiec gminachelmno + gminachlopice gminachmielnik + gminachociwel gminachocz gminachodel + gminachodow gminachojnice gminachojnow + gminachotcza + gminachrzastowice gminachrzypskowielkie gminachybie gminaciasna + gminaciechanow gminaciechocin + gminacieladz + gminacieszanow gminaciezkowice + gminacisek gminacisna gminacmolas gminacycow @@ -1570,19 +1771,26 @@ gminaczarna gminaczarnkow gminaczarnydunajec + gminaczastary gminaczechowicedziedzice gminaczernichow gminaczerniejewo + gminaczerniewice + gminaczernikowo gminaczerwionkaleszczyny - gminaczerwonka + gminaczerwonak gminaczluchow gminaczosnow + gminadabrowazielona + gminadabrowice gminadamaslawek gminadamnica gminadarlowo gminadebewielkie gminadebica gminadebno + gminadebowakloda + gminadebrzno gminadlutow gminadobczyce gminadobra @@ -1590,19 +1798,31 @@ gminadobrodzien gminadobron gminadobrzany + gminadobrzyca gminadobrzyniewoduze + gminadolsk gminadominowo gminadorohusk + gminadoruchow + gminadragacz + gminadrawsko + gminadruzbice gminadrzewica gminadubiecko + gminadubienka gminadukla gminadwikozy + gminadydnia gminadynow gminadziadowakloda gminadzialoszyce + gminadziemiany gminadzierzoniow + gminadzwola gminaelblag - gminafajslawice + gminaelk + gminafredropol + gminagarbatkaletnisko gminagarbow gminagarwolin gminagasawa @@ -1612,29 +1832,37 @@ gminagdow gminagielniow gminagieraltowice - gminaglinojeck gminaglogow + gminaglogowek gminagluchow + gminaglusk gminagluszyca gminagniew + gminagniewino gminagniewoszow gminagniezno gminagoczalkowicezdroj gminagodkowo gminagodow gminagodzieszewielkie + gminagodziszow gminagolancz + gminagolcza gminagoleszow gminagolina gminagolubdobrzyn + gminagoluchow + gminagomunice gminagoraj gminagorlice gminagorno + gminagorzyca gminagoscieradow gminagostyn gminagostynin gminagoszczyn gminagozd + gminagrabica gminagrabow gminagrabowiec gminagrabownadpilica @@ -1653,45 +1881,52 @@ gminagrudziadz gminagruta gminagrybow + gminagryfice + gminagrzmiaca gminahaczow gminahalinow gminahansk gminaharasiuki gminahazlach gminaherby + gminahorodlo gminahrubieszow gminahuszlew gminahyzne gminaimielno gminainowroclaw + gminairzadze gminaistebna + gminaiwanowice gminaiwierzyce gminaiwoniczzdroj gminaizabelin gminaizbica - gminajadow + gminaizbicko + gminajablon gminajaktorow + gminajakubow gminajanikowo + gminajanow gminajanowiec gminajanowpodlaski - gminajaraczewo + gminajarczow gminajarocin gminajasienicarosielna + gminajasliska gminajaslo gminajastkow gminajastrowie gminajastrzab gminajedlicze - gminajedlinsk - gminajedlnialetnisko gminajejkowice gminajemielnica - gminajemielno gminajerzmanowa gminajezewo gminajeziorawielkie gminajeziorzany gminajezowe + gminajoniec gminajordanow gminajozefow gminajozefownadwisla @@ -1699,14 +1934,18 @@ gminakakolewnica gminakamien gminakamienica - gminakamieniec + gminakamiennik gminakamionka gminakarczmiska gminakargowa + gminakarlino + gminakarniewo gminakaweczyn gminakazimierzbiskupi gminakepice + gminakesowo gminakielczyglow + gminakietrz gminakikol gminakiszkowo gminakleczew @@ -1721,22 +1960,30 @@ gminaklucze gminakluczewsko gminakobielewielkie + gminakobylanka gminakochanowice gminakock gminakodrab gminakolaczyce gminakolbaskowo + gminakolbiel gminakolczyglowy + gminakolobrzeg gminakoluszki gminakomancza + gminakomarowkapodlaska gminakomorniki gminakomprachcice gminakonarzyny gminakondratowice + gminakoneck gminakoniusza gminakonopiska gminakonskowola + gminakonstantynow gminakoprzywnica + gminakorfantow + gminakornik gminakorsze gminakorycin gminakorzenna @@ -1746,10 +1993,14 @@ gminakoscierzyna gminakosowlacki gminakostrzyn - gminakoszyce + gminakoszecin gminakotla gminakotun + gminakowiesy + gminakozieglowy gminakozlow + gminakramsk + gminakrasniczyn gminakrasnik gminakrasnobrod gminakrasnystaw @@ -1759,12 +2010,16 @@ gminakrosnice gminakrupskimlyn gminakruszwica + gminakrynice gminakrynki gminakrzanowice gminakrzemieniewo + gminakrzeszow gminakrzymow + gminakrzywcza gminakrzywin gminakrzyzanowice + gminaksawerow gminaksiazwielki gminakunice gminakunow @@ -1774,15 +2029,19 @@ gminakwilcz gminalabowa gminalabunie + gminalaczna + gminaladek gminalambinowice gminalanckorona + gminalancut + gminalapanow gminalapszenizne gminalasin gminalaskarzew gminalasowicewielkie gminalaszczow - gminalaszki gminalatowicz + gminalaziska gminalazy gminaleczyca gminaleczyce @@ -1792,11 +2051,14 @@ gminalelow gminalesna gminalesnapodlaska + gminalesniowice gminalesznowola gminalezajsk gminalichnowy gminalimanowa gminalinia + gminaliniewo + gminalipiany gminalipinki gminalipnik gminalipowa @@ -1804,43 +2066,51 @@ gminaliszki gminaliw gminalobez + gminalochow gminalodygowice gminalomazy + gminalomianki + gminaloniow gminalopiennikgorny gminalopuszno + gminalosice gminaluban gminalubartow gminalubasz + gminalubawka gminalubenia gminalubianka gminalubicz + gminalubien gminalubiewo gminalubin gminalubniany gminalubochnia - gminalubomia gminalubon + gminalubsza + gminalubyczakrolewska gminalukow gminalukowica gminalutowiska + gminalututow gminaluzino gminaluzna gminalysomice + gminamaciejowice gminamagnuszew + gminamajdankrolewski gminamakowpodhalanski - gminamalawies gminamalbork gminamaldyty gminamalkiniagorna gminamarcinowice gminamargonin gminamarianowo - gminamarkusy - gminamaslow + gminamarkuszow + gminamecinka gminamedyka gminamelgiew gminamichalow - gminamichalowo gminamiedzianagora gminamiedzna gminamiedzno @@ -1849,7 +2119,10 @@ gminamiedzyrzecpodlaski gminamiedzyzdroje gminamiejscepiastowe + gminamiekinia gminamielec + gminamielno + gminamieszkowice gminamilanow gminamilejow gminamilicz @@ -1858,21 +2131,26 @@ gminamiloslaw gminamilowka gminaminskmazowiecki + gminamirow gminamirsk gminamlynary + gminamodliborzyce gminamogielnica gminamogilany + gminamogilno + gminamorawica gminamordy gminamoryn gminamrocza gminamrozy + gminamsciwojow + gminamstow gminamszana gminamszanadolna gminamurow gminamycielin - gminamyslakowice + gminamykanow gminamysliborz - gminanadarzyn gminanamyslow gminanasielsk gminanawojowa @@ -1883,23 +2161,25 @@ gminaniedrzwicaduza gminaniedzwiada gminaniedzwiedz - gminaniegoslawice - gminaniwiska + gminanowakarczma gminanowaruda gminanowawiesleborska + gminanowe gminanowemiasto gminanowemiastonadwarta - gminanowogrodbobrzanski + gminanowogrod gminanowosolna gminanowykaweczyn + gminanowykorczyn gminanowystaw gminanowytarg gminanowytomysl + gminanozdrzec gminanur gminaobrazow gminaochotnicadolna gminaogrodzieniec - gminaolecko + gminaolszanica gminaolsztynek gminaolszyna gminaopatowiec @@ -1909,11 +2189,15 @@ gminaosiekjasielski gminaosiekmaly gminaosielsko + gminaosina + gminaosjakow + gminaostrorog gminaostrow gminaostrowek gminaostrowlubelski gminaostrowmazowiecka gminaostrowwielkopolski + gminaotmuchow gminaotyn gminaozarow gminaozarowice @@ -1921,27 +2205,32 @@ gminaozorkow gminapabianice gminapacanow + gminapacyna gminapaczkow gminapadewnarodowa - gminapajeczno gminapakoslaw gminapakoslawice gminapalecznica gminapanki gminaparchowo gminaparczew - gminapawlosiow + gminapaslek + gminapatnow gminapawlowice gminapawlowiczki + gminapawonkow gminapeclaw gminapelplin + gminapepowo gminapiaski gminapiatnica - gminapiecki gminapiekoszow + gminapieniezno gminapilchowice + gminapinczow gminapionki - gminapiotrkowtrybunalski + gminaplaska + gminaplaterowka gminaplesna gminapleszew gminaplonsk @@ -1949,6 +2238,7 @@ gminapoczesna gminapodedworze gminapodegrodzie + gminapodgorzyn gminapokoj gminapolajewo gminapolaniec @@ -1957,16 +2247,18 @@ gminapolice gminapolkowice gminapomiechowek + gminaponiatowa gminapopielow gminapopow - gminaporaj gminapotegowo + gminapotokwielki gminapraszka - gminaprazmow gminaprochowice gminapromna gminaproszkow + gminaprusice gminapruszczgdanski + gminaprzechlewo gminaprzeclaw gminaprzedecz gminaprzemet @@ -1976,7 +2268,9 @@ gminaprzodkowo gminaprzykona gminaprzylek + gminaprzyrow gminaprzystajn + gminaprzytoczna gminapuchaczow gminapuck gminapulawy @@ -1984,10 +2278,10 @@ gminapuszczamarianska gminapysznica gminapyzdry + gminarabawyzna gminarachanie gminaraciechowice - gminaraclawice - gminaradecznica + gminaradgoszcz gminaradkow gminaradlow gminaradomin @@ -1995,16 +2289,29 @@ gminaradomyslnadsanem gminaradoszyce gminaradwanice + gminaradymno + gminaradziejow gminaradzilow + gminarajgrod + gminarakow + gminarakszawa gminarawamazowiecka + gminaregnow gminarenskawies + gminarogozno + gminarokitno + gminaropa gminarossosz gminarozprza gminarucianenida gminarudahuta gminarudna gminarudniki + gminarudniknadsanem + gminarudziniec gminarusiec + gminarusinow + gminarybczewice gminarychliki gminarychtal gminaryczywol @@ -2012,32 +2319,39 @@ gminarypin gminarytro gminarytwiany - gminarzasnia gminarzeczyca gminarzepiennikstrzyzewski gminarzepin + gminarzezawa gminarzgow gminasadki gminasadowne gminasamborzec gminasanok + gminasawin gminascinawa gminasedziejowice + gminasejny + gminasekowa gminasepopol gminaserokomla gminasianow gminasicienko gminasieciechow gminasiedlce + gminasiedliszcze gminasiemiatycze + gminasiemien gminasiemysl gminasiennica gminasiennicarozana gminasienno gminasiepraw gminasieradz + gminasierakow gminasierakowice gminasiewierz + gminasitkowkanowiny gminasitno gminaskarzyskokoscielne gminaskepe @@ -2045,16 +2359,22 @@ gminaskoczow gminaskoki gminaskolyszyn + gminaskrwilno gminaskrzyszow gminaskulsk + gminaskwierzyna gminaslawa + gminasliwice gminaslopnice + gminaslubice gminaslupca gminaslupia + gminaslupiakonecka + gminasmigiel gminasobieniejeziory + gminasobolew gminasobotka gminasokolka - gminasokoly gminasolina gminasosnicowice gminasosnie @@ -2069,11 +2389,16 @@ gminastaremiasto gminastarepole gminastarogardgdanski + gminastarybrus + gminastarydzierzgon + gminastarytarg gminastawiszyn + gminastepnica gminastoczeklukowski gminastopnica gminastrawczyn gminastrykow + gminastryszawa gminastryszow gminastrzalkowo gminastrzelceopolskie @@ -2082,38 +2407,43 @@ gminastrzyzewice gminastupsk gminasubkowy + gminasuchan gminasuchedniow gminasuchozebry gminasuchylas gminasulechow gminasulecin + gminasulejow gminasulikow gminasulmierzyce gminasulow gminasusiec - gminaswierklaniec + gminaswiercze + gminaswierczow + gminaswierklany gminaswierzawa gminaswieszyno gminaswilcza gminaszadek gminaszaflary gminaszastarka + gminaszczawinkoscielny gminaszczebrzeszyn gminaszczekociny gminaszczercow - gminaszczutowo gminaszczytna gminaszczytniki - gminaszemud + gminaszczytno gminaszerzyny gminaszlichtyngowa + gminaszrensk + gminaszudzialowo gminaszydlow gminatarlow gminatarnow gminatarnowiec gminatarnowopolski gminateresin - gminaterespol gminatereszpol gminatluchowo gminatluszcz @@ -2123,12 +2453,15 @@ gminatoszek gminatrabkiwielkie gminatrzebiatow + gminatrzebielino gminatrzebinia - gminatrzeszczany gminatrzyciaz + gminatrzydnikduzy gminatuchow + gminatulowice gminaturosnkoscielna gminatuszownarodowy + gminatworog gminatyczyn gminatymbark gminatyrawawoloska @@ -2137,15 +2470,21 @@ gminaulanmajorat gminaulanow gminaulez + gminaulhowek gminaurszulin gminaurzedow + gminausciegorlickie gminauscimow gminawachock + gminawadrozewielkie gminawagrowiec + gminawalce gminawapielsk gminawasilkow + gminawasosz gminawawolnica gminawejherowo + gminawerbkowice gminawiazow gminawiazowna gminawicko @@ -2153,16 +2492,21 @@ gminawielbark gminawielen gminawielgie + gminawielgomlyny gminawieliszew gminawielkanieszawka gminawieniawa gminawieprz gminawieruszow + gminawierzbinek gminawierzbno + gminawierzchlas gminawierzchoslawice gminawietrzychowice gminawijewo + gminawilczyce gminawilczyn + gminawilkolaz gminawilkow gminawilkowice gminawinnica @@ -2173,12 +2517,14 @@ gminawitkowo gminawladyslawow gminawlen + gminawloclawek gminawlodawa gminawloszczowa gminawodzierady gminawodzislaw gminawojcieszkow gminawojnicz + gminawojslawice gminawolakrzysztoporska gminawolanow gminawolbrom @@ -2189,15 +2535,18 @@ gminawreczycawielka gminawronki gminawyrzysk - gminazabierzow + gminawysokie gminazabno gminazagan - gminazagorz + gminazagorow gminazaklikow gminazakroczym gminazakrzowek + gminazalesie gminazaleszany + gminazaluski gminazamosc + gminazarnow gminazarnowiec gminazarow gminazarszyn @@ -2209,20 +2558,26 @@ gminazbojno gminazbroslawice gminazdunskawola - gminazduny gminazdzieszowice + gminazebowice gminazebrzydowice + gminazegocina gminazelazkow + gminazembrzyce gminazgierz gminazgorzelec gminaziebice gminazielonki gminazlawieswielka + gminazlota + gminazlotnikikujawskie gminazmudz gminaznin gminazolkiewka gminazolynia gminazukowice + gminazurawica + gminazyrakow gminazyrzyn gminazytno gniezno @@ -2231,19 +2586,19 @@ gora gorakalwaria gorlice + gorzno gorzowslaski gorzowwielkopolski gostynin grajewo grodziskmazowiecki - gronowoelblaskie grudziadz + grybow gryfino gryfowslaski hel hrubieszow inowroclaw - iwanowice izbicakujawska jablonowopomorskie janowiecwielkopolski @@ -2253,7 +2608,6 @@ jaslo jastrzebiezdroj jawor - jaworzno jedlinazdroj jelczlaskowice jeleniagora @@ -2274,9 +2628,9 @@ kepno ketrzyn kielce - kielczyglow klodawa klodzko + kluczbork knurow kobylka kolo @@ -2306,11 +2660,13 @@ krzeszowice krzyzwielkopolski ksiazwielkopolski - kudowazdroj kujawskopomorskie + kutno kuzniaraciborska + kwidzyn labiszyn ladekzdroj + lancut lapy lask laskarzew @@ -2322,10 +2678,10 @@ legnica leszno lewinbrzeski + lewinbrzeski2 lezajsk limanowa lipno - lipsko lodz lodzkie lowicz @@ -2342,11 +2698,8 @@ lwowekslaski malbork malopolskie - marciszow marki - maslowice mazowieckie - miastko michalowice miechow miedzyrzecpodlaski @@ -2354,6 +2707,7 @@ mielec milanowek minskmazowiecki + mniszkow mosina mragowo mragowski @@ -2363,18 +2717,17 @@ myslowice myszkow naklonadnotecia - nasielsk niemodlin niepolomice nisko nowadeba nowasarzyna + nowasol nowemiasteczko noweskalmierzyce nowogard nowogrodbobrzanski nowogrodziec - nowosolna nowydwormazowiecki nowysacz nowytarg @@ -2388,6 +2741,8 @@ opoczno opole opolelubelskie + opolskie + orzesze osieczna osiecznica ostroda @@ -2405,13 +2760,16 @@ piekaryslaskie piensk pila + pilzno piotrkowtrybunalski pisz plock plonsk pniewy + pobiedziska podkarpackie podkowalesna + podlaskie polczynzdroj pomorskie poniec @@ -2420,7 +2778,7 @@ powiataugustowski powiatbedzinski powiatbelchatowski - powiatbialobrzeski + powiatbialostocki powiatbialski powiatbielski powiatbieszczadzki @@ -2446,6 +2804,7 @@ powiatczluchowski powiatdabrowski powiatdebicki + powiatdrawski powiatdzialdowski powiatdzierzoniowski powiatelblaski @@ -2460,10 +2819,11 @@ powiatgoleniowski powiatgolubskodobrzynski powiatgorlicki + powiatgorowski powiatgorzowski powiatgostynski + powiatgrajewski powiatgrojecki - powiatgrudziadzki powiatgryficki powiatgryfinski powiathajnowski @@ -2478,6 +2838,7 @@ powiatjedrzejowski powiatjeleniogorski powiatkaliski + powiatkamiennogorski powiatkamienski powiatkartuski powiatkazimierski @@ -2502,6 +2863,7 @@ powiatkrosnienski powiatkrotoszynski powiatkutnowski + powiatlancucki powiatlaski powiatleborski powiatleczycki @@ -2516,6 +2878,7 @@ powiatlipski powiatlobeski powiatlodzkiwschodni + powiatlosicki powiatlowicki powiatlubaczowski powiatlubanski @@ -2536,9 +2899,11 @@ powiatmyslenicki powiatmyszkowski powiatnakielski + powiatnamyslowski powiatnidzicki powiatnizanski powiatnowodworski + powiatnowomiejski powiatnowosadecki powiatnowosolski powiatnowotarski @@ -2574,7 +2939,6 @@ powiatproszowicki powiatprudnicki powiatpruszkowski - powiatprzasnyski powiatprzemyski powiatprzeworski powiatprzysuski @@ -2597,6 +2961,7 @@ powiatrzeszowski powiatsandomierski powiatsanocki + powiatsejnenski powiatsepolenski powiatsiedlecki powiatsiemiatycki @@ -2624,6 +2989,7 @@ powiatswidnicki powiatswidwinski powiatswiebodzinski + powiatswiecki powiatszamotulski powiatszczycienski powiatsztumski @@ -2642,6 +3008,7 @@ powiatwagrowiecki powiatwalecki powiatwarszawskizachodni + powiatwegorzewski powiatwegrowski powiatwejherowski powiatwielicki @@ -2661,7 +3028,6 @@ powiatwyszkowski powiatzabkowicki powiatzaganski - powiatzambrowski powiatzamojski powiatzarski powiatzawiercianski @@ -2672,34 +3038,33 @@ powiatzlotoryjski powiatzlotowski powiatzninski - powiatzurominski powiatzyrardowski powiatzywiecki poznan - prostki proszowice prudnik + pruszczgdanski pruszkow przasnysz przemysl przeworsk przysucha pszczyna - pszow puck pulawy + pultusk + puszczykowo pyskowice rabkazdroj raciaz raciborz - raciechowice radom radomsko + radomyslwielki radymno radziejow radzionkow radzynpodlaski - rakow rawamazowiecka rawicz reda @@ -2713,6 +3078,7 @@ rymanow rypin rzeszow + rzeszowprojekt sandomierz sanok sedziszowmalopolski @@ -2720,7 +3086,6 @@ siedlce siemianowiceslaskie siemiatycze - sieniawa sieradz skarzyskokamienna skawina @@ -2739,17 +3104,15 @@ srodaslaska srodawielkopolska starachowice + stargard starogardgdanski starysacz staszow stronieslaskie - strzegom strzyzow - suchylas sulejowek sulkowice sulmierzyce - suwalski swarzedz swidnica swidnik @@ -2772,8 +3135,10 @@ terespol tomaszowlubelski tomaszowmazowiecki + tomaszowmazowieckiprojekt torun trzcianka + trzcinskozdroj trzebnica trzemeszno tuliszkow @@ -2786,7 +3151,7 @@ ustrzykidolne wadowice wagrowiec - walbrzych + walcz warminskomazurskie warszawa wasosz @@ -2795,7 +3160,7 @@ wiecbork wieliczka wielkopolskie - wizna + wielun wladyslawowo wloclawek wlodawa @@ -2824,6 +3189,7 @@ zielonagora zielonka zlotoryja + zlotow zory zwolen zyrardow 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 cc31d8933..18249ba8b 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.migrations import android.content.Context import androidx.preference.PreferenceManager import androidx.room.Room +import androidx.room.migration.Migration import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.core.app.ApplicationProvider @@ -16,7 +17,7 @@ abstract class AbstractMigrationTest { val dbName = "migration-test" - val context: Context get() = ApplicationProvider.getApplicationContext() + private val context: Context get() = ApplicationProvider.getApplicationContext() @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( @@ -25,6 +26,10 @@ abstract class AbstractMigrationTest { FrameworkSQLiteOpenHelperFactory() ) + fun runMigrationsAndValidate(migration: Migration) { + helper.runMigrationsAndValidate(dbName, migration.endVersion, true, migration).close() + } + fun getMigratedRoomDatabase(): AppDatabase { val database = Room.databaseBuilder( ApplicationProvider.getApplicationContext(), 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 a02904733..f614c8ca9 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 @@ -33,7 +33,7 @@ class Migration12Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 12, true, Migration12()) + runMigrationsAndValidate(Migration12()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -49,6 +49,7 @@ class Migration12Test : AbstractMigrationTest() { assertEquals(2, studentId) assertEquals(6, classId) } + db.close() } @Test @@ -62,7 +63,7 @@ class Migration12Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 12, true, Migration12()) + runMigrationsAndValidate(Migration12()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -73,6 +74,7 @@ class Migration12Test : AbstractMigrationTest() { assertEquals(2, studentId) assertEquals(1, classId) } + db.close() } @Test @@ -88,7 +90,7 @@ class Migration12Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 12, true, Migration12()) + runMigrationsAndValidate(Migration12()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -107,6 +109,7 @@ class Migration12Test : AbstractMigrationTest() { assertEquals(studentId, 3) assertEquals(true, isCurrent) } + db.close() } private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, isCurrent: Boolean) { 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 bdfb4137d..b0c03fb11 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 @@ -57,6 +57,8 @@ class Migration13Test : AbstractMigrationTest() { assertEquals("C", className) assertEquals("Publiczna szkoła Wulkanowego-fejka nr 2 w fakelog.cf", schoolName) } + + db.close() } @Test @@ -85,6 +87,8 @@ class Migration13Test : AbstractMigrationTest() { assertEquals("", className) assertEquals("Publiczna szkoła Wulkanowego-fejka nr 1 w fakelog.cf", schoolName) } + + db.close() } @Test @@ -148,6 +152,7 @@ class Migration13Test : AbstractMigrationTest() { assertFalse(semesters[2].second) assertTrue(semesters[3].second) } + db.close() } private fun getSemesters(db: SupportSQLiteDatabase, query: String): List> { diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt index 8e744f27a..19eda9ba8 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration27Test.kt @@ -27,7 +27,7 @@ class Migration27Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 27, true, Migration27()) + runMigrationsAndValidate(Migration27()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -39,6 +39,8 @@ class Migration27Test : AbstractMigrationTest() { assertEquals(123, userLoginId) assertEquals("Student Jan", userName) } + + db.close() } @Test @@ -49,7 +51,7 @@ class Migration27Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 27, true, Migration27()) + runMigrationsAndValidate(Migration27()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -61,6 +63,8 @@ class Migration27Test : AbstractMigrationTest() { assertEquals(2, userLoginId) assertEquals("Unit Jan", userName) } + + db.close() } @Test @@ -73,7 +77,7 @@ class Migration27Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 27, true, Migration27()) + runMigrationsAndValidate(Migration27()) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -90,6 +94,8 @@ class Migration27Test : AbstractMigrationTest() { assertEquals(333, userLoginId) assertEquals("Unit Tomasz", userName) } + + db.close() } private fun createStudent(db: SupportSQLiteDatabase, id: Long, userLoginId: Int, studentName: String) { diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt index 883cdb81c..79c24f2e6 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration35Test.kt @@ -29,7 +29,7 @@ class Migration35Test : AbstractMigrationTest() { close() } - helper.runMigrationsAndValidate(dbName, 35, true, Migration35(AppInfo())) + runMigrationsAndValidate(Migration35(AppInfo())) val db = getMigratedRoomDatabase() val students = runBlocking { db.studentDao.loadAll() } @@ -38,6 +38,8 @@ class Migration35Test : AbstractMigrationTest() { assertTrue { students[0].avatarColor in AppInfo().defaultColorsForAvatar } assertTrue { students[1].avatarColor in AppInfo().defaultColorsForAvatar } + + db.close() } private fun createStudent(db: SupportSQLiteDatabase, id: Long) { diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt new file mode 100644 index 000000000..1855e0d50 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt @@ -0,0 +1,130 @@ +package io.github.wulkanowy.data.db.migrations + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import android.os.Build +import androidx.sqlite.db.SupportSQLiteDatabase +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.random.Random +import kotlin.test.assertEquals + +@HiltAndroidTest +@RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class) +class Migration54Test : AbstractMigrationTest() { + + @Test + fun `don't touch unrelated students`() = runTest { + with(helper.createDatabase(dbName, 53)) { + createStudent(1, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz") + createStudent(2, ADFSLight, "umt.tarnow.pl", "tarnow", "Joanna Marcinkiewicz") + close() + } + + runMigrationsAndValidate(Migration54()) + val db = getMigratedRoomDatabase() + val students = db.studentDao.loadAll() + + assertEquals(2, students.size) + with(students[0]) { + assertEquals(STANDARD.name, loginType) + assertEquals("https://vulcan.net.pl", scrapperBaseUrl) + assertEquals("rzeszow", symbol) + } + with(students[1]) { + assertEquals(ADFSLight.name, loginType) + assertEquals("https://umt.tarnow.pl", scrapperBaseUrl) + assertEquals("tarnow", symbol) + } + db.close() + } + + @Test + fun `remove tomaszow mazowiecki students`() = runTest { + with(helper.createDatabase(dbName, 53)) { + createStudent(1, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz") + createStudent(2, STANDARD, "vulcan.net.pl", "tomaszowmazowiecki", "Joanna Stec") + createStudent(3, STANDARD, "vulcan.net.pl", "tomaszowmazowiecki", "Kacper Morawiecki") + close() + } + + runMigrationsAndValidate(Migration54()) + val db = getMigratedRoomDatabase() + val students = db.studentDao.loadAll() + assertEquals(1, students.size) + with(students[0]) { + assertEquals("rzeszow", symbol) + } + db.close() + } + + @Test + fun `migrate resman students`() = runTest { + with(helper.createDatabase(dbName, 53)) { + createStudent(1, ADFSLight, "resman.pl", "rzeszow", "Joanna Stec") + createStudent(2, ADFSLight, "resman.pl", "rzeszow", "Kacper Morawiecki") + createStudent(3, STANDARD, "vulcan.net.pl", "rzeszow", "Jan Michniewicz") + close() + } + runMigrationsAndValidate(Migration54()) + val db = getMigratedRoomDatabase() + val students = db.studentDao.loadAll() + assertEquals(3, students.size) + with(students[0]) { + assertEquals(ADFSLightScoped.name, loginType) + assertEquals("https://vulcan.net.pl", scrapperBaseUrl) + assertEquals("rzeszowprojekt", symbol) + } + with(students[1]) { + assertEquals(ADFSLightScoped.name, loginType) + assertEquals("https://vulcan.net.pl", scrapperBaseUrl) + assertEquals("rzeszowprojekt", symbol) + } + db.close() + } + + private fun SupportSQLiteDatabase.createStudent( + id: Long, + loginType: Sdk.ScrapperLoginType, + host: String, + symbol: String, + studentName: String, + ) { + insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { + put("scrapper_base_url", "https://$host") + put("mobile_base_url", "") + put("login_type", loginType.name) + put("login_mode", "SCRAPPER") + put("certificate_key", "") + put("private_key", "") + put("is_parent", false) + put("email", "jan@fakelog.cf") + put("password", "******") + put("symbol", symbol) + put("student_id", Random.nextInt()) + put("user_login_id", id) + put("user_name", studentName) + put("student_name", studentName) + put("school_id", "123") + put("school_short", "") + put("school_name", "") + put("class_name", "") + put("class_id", Random.nextInt()) + put("is_current", false) + put("registration_date", "0") + put("id", id) + put("nick", "") + put("avatar_color", "") + }) + } +} diff --git a/build.gradle b/build.gradle index e8e1052b6..174a3cb6d 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ allprojects { mavenCentral() google() maven { url "https://jitpack.io" } + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://developer.huawei.com/repo/" } } } From 2fa26c37a95a0fa34765c6de73121eb26dffe094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 20 Dec 2022 21:55:46 +0100 Subject: [PATCH 084/545] Revert "Fix app name in french (#2072)" This reverts commit 277ffd22be786cfa429ea1d61827e8cb62d1ea56. --- app/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b0865896e..6b496d85a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -237,7 +237,7 @@ dependencies { implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:2.2.2" - implementation "io.github.wulkanowy:AppKillerManager:3.0.1" + implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 8722 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f37..d7e66b5c6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 7588345b6d3260eb9b024b7e2e627a86e79c1f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 20 Dec 2022 23:35:47 +0100 Subject: [PATCH 085/545] Bump sdk --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6b496d85a..08e0ed921 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.8.2-SNAPSHOT" + implementation "io.github.wulkanowy:sdk:c74512daa7" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' From 61240777cf3d70989e0e944f953c87dc594bd564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 21 Dec 2022 00:00:03 +0100 Subject: [PATCH 086/545] Version 1.8.2 --- .github/workflows/test.yml | 5 ++++- README.cs.md | 2 +- README.de.md | 2 +- README.en.md | 2 +- README.md | 2 +- README.sk.md | 2 +- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 10 ++++------ 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13875078a..7f8591bb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,10 @@ name: Tests on: push: - branches: [ master, develop ] + branches: + - master + - develop + - 'hotfix/**' tags: [ '*' ] pull_request: diff --git a/README.cs.md b/README.cs.md index d3d4e2557..8171b27d6 100644 --- a/README.cs.md +++ b/README.cs.md @@ -2,7 +2,7 @@ # Wulkanowy -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) diff --git a/README.de.md b/README.de.md index 853abd13e..972f66ba9 100644 --- a/README.de.md +++ b/README.de.md @@ -2,7 +2,7 @@ # Wulkanowy -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) diff --git a/README.en.md b/README.en.md index 7877bf377..6e4da4637 100644 --- a/README.en.md +++ b/README.en.md @@ -2,7 +2,7 @@ # Wulkanowy -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) diff --git a/README.md b/README.md index 09480e7d7..f3d2e29a2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Wulkanowy -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) diff --git a/README.sk.md b/README.sk.md index 64786556e..ff0c6e3c9 100644 --- a/README.sk.md +++ b/README.sk.md @@ -2,7 +2,7 @@ # Wulkanowy -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) diff --git a/app/build.gradle b/app/build.gradle index 08e0ed921..a61d0a1d4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 32 - versionCode 116 - versionName "1.8.1" + versionCode 117 + versionName "1.8.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,7 +162,7 @@ play { track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS userFraction = 0.10d - updatePriority = 4 + updatePriority = 5 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:c74512daa7" + implementation "io.github.wulkanowy:sdk:1.8.2" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 7b2fda861..c4f4c6cfb 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 1.8.1 +Wersja 1.8.2 -- naprawiliśmy liczenie średniej ucznia w ocenach klasy dla wykresu "Wszystkie" -- zmieniliśmy kolejność przycisków akcji w podglądzie wiadomości -- ulepszyliśmy oznaczenie nieodczytanych wiadomości -- naprawiliśmy pokazywanie informacji o odczytanej wiadomości w wysłanych -- dodaliśmy opcję ręcznego wybierania skrzynki pocztowej +- naprawiliśmy logowanie dla użytkowników systemu Resman Rzeszów +- dodaliśmy wsparcie dla nowej platformy z Tomaszowa Mazowieckiego +- naprawiliśmy literówkę w tytule wiadomości z szablonem usprawiedliwienia Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From ede5914d709a5ac1af1d6056b301eba5d8c7e9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 21 Dec 2022 00:31:29 +0100 Subject: [PATCH 087/545] Automatically show current student mailbox only when there is only one mailbox available (#2085) * Automatically show current student mailbox only when there is only one mailbox for this student available * Fallback to 'unknown' mailbox key if there is no matching mailbox to message --- .../wulkanowy/data/mappers/MessageMapper.kt | 6 ++++- .../messages/GetMailboxByStudentUseCase.kt | 5 +++- .../domain/GetMailboxByStudentUseCaseTest.kt | 26 +++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 120eb183a..2ede5aa1b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.sdk.pojo.MailboxType +import timber.log.Timber import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient @@ -16,7 +17,10 @@ fun List.mapToEntities( mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> box.fullName == it.mailbox }?.globalKey.let { mailboxKey -> - requireNotNull(mailboxKey) { "Can't find ${it.mailbox} in $allMailboxes" } + if (mailboxKey == null) { + Timber.e("Can't find ${it.mailbox} in $allMailboxes") + "unknown" + } else mailboxKey }, email = student.email, messageId = it.id, diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt index a696d9b2f..669514aae 100644 --- a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt +++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt @@ -17,8 +17,11 @@ class GetMailboxByStudentUseCase @Inject constructor( private fun List.filterByStudent(student: Student): Mailbox? { val normalizedStudentName = student.studentName.normalizeStudentName() - return find { + return singleOrNull { it.studentName.normalizeStudentName() == normalizedStudentName + } ?: singleOrNull { + it.studentName.normalizeStudentName() == normalizedStudentName + && it.schoolNameShort == student.schoolShortName } ?: singleOrNull { it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() } ?: singleOrNull { 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 029800266..6db16d2f5 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -160,8 +160,29 @@ class GetMailboxByStudentUseCaseTest { assertEquals(expectedMailbox, systemUnderTest(student)) } + @Test + fun `get mailbox for student with mailboxes from two different schools`() = runTest { + val student = getStudentEntity( + userName = "Kamil Bednarek", + studentName = "Kamil Bednarek", + schoolShortName = "CKZiU", + ) + val mailbox1 = getMailboxEntity( + studentName = "Kamil Bednarek", + schoolShortName = "ZSTiO", + ) + val mailbox2 = getMailboxEntity( + studentName = "Kamil Bednarek", + schoolShortName = "CKZiU", + ) + coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox1, mailbox2) + + assertEquals(mailbox2, systemUnderTest(student)) + } + private fun getMailboxEntity( studentName: String, + schoolShortName: String = "test", ) = Mailbox( globalKey = "", fullName = "", @@ -170,13 +191,14 @@ class GetMailboxByStudentUseCaseTest { schoolId = "", symbol = "", studentName = studentName, - schoolNameShort = "", + schoolNameShort = schoolShortName, type = MailboxType.STUDENT, ) private fun getStudentEntity( studentName: String, userName: String, + schoolShortName: String = "test", ) = Student( scrapperBaseUrl = "http://fakelog.cf", email = "jan@fakelog.cf", @@ -192,7 +214,7 @@ class GetMailboxByStudentUseCaseTest { privateKey = "", registrationDate = Instant.now(), schoolName = "", - schoolShortName = "test", + schoolShortName = schoolShortName, schoolSymbol = "", studentId = 1, studentName = studentName, From aa3d7e37fcf7acadeb5dfd1f1b2f76ac31b8b496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 21 Dec 2022 00:31:29 +0100 Subject: [PATCH 088/545] Automatically show current student mailbox only when there is only one mailbox available (#2085) * Automatically show current student mailbox only when there is only one mailbox for this student available * Fallback to 'unknown' mailbox key if there is no matching mailbox to message --- .../wulkanowy/data/mappers/MessageMapper.kt | 6 ++++- .../messages/GetMailboxByStudentUseCase.kt | 5 +++- .../domain/GetMailboxByStudentUseCaseTest.kt | 26 +++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 120eb183a..2ede5aa1b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.sdk.pojo.MailboxType +import timber.log.Timber import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient @@ -16,7 +17,10 @@ fun List.mapToEntities( mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box -> box.fullName == it.mailbox }?.globalKey.let { mailboxKey -> - requireNotNull(mailboxKey) { "Can't find ${it.mailbox} in $allMailboxes" } + if (mailboxKey == null) { + Timber.e("Can't find ${it.mailbox} in $allMailboxes") + "unknown" + } else mailboxKey }, email = student.email, messageId = it.id, diff --git a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt index a696d9b2f..669514aae 100644 --- a/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt +++ b/app/src/main/java/io/github/wulkanowy/domain/messages/GetMailboxByStudentUseCase.kt @@ -17,8 +17,11 @@ class GetMailboxByStudentUseCase @Inject constructor( private fun List.filterByStudent(student: Student): Mailbox? { val normalizedStudentName = student.studentName.normalizeStudentName() - return find { + return singleOrNull { it.studentName.normalizeStudentName() == normalizedStudentName + } ?: singleOrNull { + it.studentName.normalizeStudentName() == normalizedStudentName + && it.schoolNameShort == student.schoolShortName } ?: singleOrNull { it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart() } ?: singleOrNull { 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 029800266..6db16d2f5 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -160,8 +160,29 @@ class GetMailboxByStudentUseCaseTest { assertEquals(expectedMailbox, systemUnderTest(student)) } + @Test + fun `get mailbox for student with mailboxes from two different schools`() = runTest { + val student = getStudentEntity( + userName = "Kamil Bednarek", + studentName = "Kamil Bednarek", + schoolShortName = "CKZiU", + ) + val mailbox1 = getMailboxEntity( + studentName = "Kamil Bednarek", + schoolShortName = "ZSTiO", + ) + val mailbox2 = getMailboxEntity( + studentName = "Kamil Bednarek", + schoolShortName = "CKZiU", + ) + coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox1, mailbox2) + + assertEquals(mailbox2, systemUnderTest(student)) + } + private fun getMailboxEntity( studentName: String, + schoolShortName: String = "test", ) = Mailbox( globalKey = "", fullName = "", @@ -170,13 +191,14 @@ class GetMailboxByStudentUseCaseTest { schoolId = "", symbol = "", studentName = studentName, - schoolNameShort = "", + schoolNameShort = schoolShortName, type = MailboxType.STUDENT, ) private fun getStudentEntity( studentName: String, userName: String, + schoolShortName: String = "test", ) = Student( scrapperBaseUrl = "http://fakelog.cf", email = "jan@fakelog.cf", @@ -192,7 +214,7 @@ class GetMailboxByStudentUseCaseTest { privateKey = "", registrationDate = Instant.now(), schoolName = "", - schoolShortName = "test", + schoolShortName = schoolShortName, schoolSymbol = "", studentId = 1, studentName = studentName, From 67875b1a9a230d0a9950718569391d2d39149116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 21 Dec 2022 13:29:32 +0100 Subject: [PATCH 089/545] Version 1.8.3 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a61d0a1d4..4cde3c294 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 32 - versionCode 117 - versionName "1.8.2" + versionCode 118 + versionName "1.8.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.8.2" + implementation "io.github.wulkanowy:sdk:1.8.3" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 c4f4c6cfb..5a47ddc7e 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 1.8.2 +Wersja 1.8.3 - naprawiliśmy logowanie dla użytkowników systemu Resman Rzeszów - dodaliśmy wsparcie dla nowej platformy z Tomaszowa Mazowieckiego +- poprawiliśmy dopasowywanie skrzynek pocztowych do uczniów - naprawiliśmy literówkę w tytule wiadomości z szablonem usprawiedliwienia Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 510e2d5b88f269663efb68e43262860ffacce028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 25 Dec 2022 04:40:58 +0100 Subject: [PATCH 090/545] Fix html entities parsing in school announcements (#2086) --- app/build.gradle | 1 + .../schoolannouncement/SchoolAnnouncementAdapter.kt | 4 ++-- .../schoolannouncement/SchoolAnnouncementDialog.kt | 4 ++-- .../java/io/github/wulkanowy/utils/StringExtension.kt | 10 +++++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e2e6dae49..8409574e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -240,6 +240,7 @@ dependencies { implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0' + implementation 'org.apache.commons:commons-text:1.10.0' playImplementation platform('com.google.firebase:firebase-bom:31.1.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt index 62f6251ec..46999599b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt @@ -2,10 +2,10 @@ package io.github.wulkanowy.ui.modules.schoolannouncement import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.text.parseAsHtml import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding +import io.github.wulkanowy.utils.parseUonetHtml import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject @@ -28,7 +28,7 @@ class SchoolAnnouncementAdapter @Inject constructor() : with(holder.binding) { schoolAnnouncementItemDate.text = item.date.toFormattedString() schoolAnnouncementItemType.text = item.subject - schoolAnnouncementItemContent.text = item.content.parseAsHtml() + schoolAnnouncementItemContent.text = item.content.parseUonetHtml() root.setOnClickListener { onItemClickListener(item) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt index 0a71afef1..e33a48f03 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt @@ -5,11 +5,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.core.text.parseAsHtml import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.utils.parseUonetHtml import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString @@ -46,7 +46,7 @@ class SchoolAnnouncementDialog : DialogFragment() { with(binding) { announcementDialogSubjectValue.text = announcement.subject announcementDialogDateValue.text = announcement.date.toFormattedString() - announcementDialogDescriptionValue.text = announcement.content.parseAsHtml() + announcementDialogDescriptionValue.text = announcement.content.parseUonetHtml() announcementDialogClose.setOnClickListener { dismiss() } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt index bddd7df4c..8043e3659 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/StringExtension.kt @@ -1,7 +1,15 @@ package io.github.wulkanowy.utils +import androidx.core.text.parseAsHtml +import org.apache.commons.text.StringEscapeUtils + inline fun String?.ifNullOrBlank(defaultValue: () -> String) = if (isNullOrBlank()) defaultValue() else this fun String.capitalise() = - replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } \ No newline at end of file + replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + +fun String.parseUonetHtml() = this + .let(StringEscapeUtils::unescapeHtml4) + .replace("\n", "
") + .parseAsHtml() From 9cedab979c18deb67d2e5612c063deec7ecd2419 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 20:07:25 +0000 Subject: [PATCH 091/545] Bump robolectric from 4.9 to 4.9.1 (#2088) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8409574e5..81a8f6726 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -264,7 +264,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.9' + testImplementation 'org.robolectric:robolectric:4.9.1' testImplementation "androidx.test:runner:1.5.1" testImplementation "androidx.test.ext:junit:1.1.4" testImplementation "androidx.test:core:1.5.0" From 7efd10665828a5bff27938dada267b08e078d82f Mon Sep 17 00:00:00 2001 From: Patryk <43276401+Zaptyp@users.noreply.github.com> Date: Sun, 1 Jan 2023 12:16:09 +0100 Subject: [PATCH 092/545] Update date in LICENSE file (#2089) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c97032f74..a1fc37058 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2022 Wulkanowy + Copyright 2023 Wulkanowy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 83974b6550877195d48bbe22bc5b9b263767de2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 1 Jan 2023 20:21:28 +0100 Subject: [PATCH 093/545] Fix NPE when trying to remove a message from mailbox that doesn't match any student (#2090) --- .../io/github/wulkanowy/data/repositories/MessageRepository.kt | 2 +- .../ui/modules/message/preview/MessagePreviewPresenter.kt | 2 +- 2 files changed, 2 insertions(+), 2 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 f95b8dbec..6dfc3ab73 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 @@ -178,7 +178,7 @@ class MessageRepository @Inject constructor( ).first() } - suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) { + suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) { deleteMessages(student, mailbox, listOf(message)) } 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 fd75f6f3a..56f23b6fa 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 @@ -186,7 +186,7 @@ class MessagePreviewPresenter @Inject constructor( runCatching { val student = studentRepository.getCurrentStudent(decryptPass = true) val mailbox = messageRepository.getMailboxByStudent(student) - messageRepository.deleteMessage(student, mailbox!!, message!!) + messageRepository.deleteMessage(student, mailbox, message!!) } .onFailure { retryCallback = { onMessageDelete() } From 897eac050a4869b4441ffef69ad8ad310e41f4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 1 Jan 2023 20:26:32 +0100 Subject: [PATCH 094/545] Refactor student selection screen (#2087) --- app/build.gradle | 2 +- .../data/mappers/RegisterUserMapper.kt | 87 +++++++ .../wulkanowy/data/pojos/RegisterUser.kt | 43 ++++ .../data/repositories/StudentRepository.kt | 10 + .../ui/modules/login/LoginActivity.kt | 9 +- .../wulkanowy/ui/modules/login/LoginData.kt | 1 + .../login/advanced/LoginAdvancedFragment.kt | 6 +- .../login/advanced/LoginAdvancedPresenter.kt | 88 ++++++- .../login/advanced/LoginAdvancedView.kt | 3 +- .../modules/login/form/LoginFormFragment.kt | 6 +- .../modules/login/form/LoginFormPresenter.kt | 11 +- .../ui/modules/login/form/LoginFormView.kt | 4 +- .../LoginStudentSelectAdapter.kt | 199 +++++++++++--- .../LoginStudentSelectFragment.kt | 72 +++--- .../studentselect/LoginStudentSelectItem.kt | 50 ++++ .../LoginStudentSelectPresenter.kt | 243 +++++++++++++++--- .../studentselect/LoginStudentSelectView.kt | 10 +- .../login/symbol/LoginSymbolFragment.kt | 18 +- .../login/symbol/LoginSymbolPresenter.kt | 37 ++- .../modules/login/symbol/LoginSymbolView.kt | 9 +- .../layout/fragment_login_student_select.xml | 102 ++------ ...gin_student_select_empty_symbol_header.xml | 38 +++ ...tem_login_student_select_header_school.xml | 38 +++ ...tem_login_student_select_header_symbol.xml | 38 +++ .../layout/item_login_student_select_help.xml | 61 +++++ ... => item_login_student_select_student.xml} | 27 +- app/src/main/res/values/strings.xml | 5 +- .../login/form/LoginFormPresenterTest.kt | 60 ++--- .../LoginStudentSelectPresenterTest.kt | 124 ++++++--- 29 files changed, 1052 insertions(+), 349 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt create mode 100644 app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml create mode 100644 app/src/main/res/layout/item_login_student_select_header_school.xml create mode 100644 app/src/main/res/layout/item_login_student_select_header_symbol.xml create mode 100644 app/src/main/res/layout/item_login_student_select_help.xml rename app/src/main/res/layout/{item_login_student_select.xml => item_login_student_select_student.xml} (71%) diff --git a/app/build.gradle b/app/build.gradle index 81a8f6726..b13f8da35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.8.3" + implementation "io.github.wulkanowy:sdk:a3b97edd48" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 new file mode 100644 index 000000000..2dfd7e062 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RegisterUserMapper.kt @@ -0,0 +1,87 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.* +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.mapper.mapSemesters +import java.time.Instant +import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent +import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser + +fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser( + email = email, + login = login, + password = password, + baseUrl = baseUrl, + loginType = loginType, + symbols = symbols.map { registerSymbol -> + RegisterSymbol( + symbol = registerSymbol.symbol, + error = registerSymbol.error, + userName = registerSymbol.userName, + schools = registerSymbol.schools.map { + RegisterUnit( + userLoginId = it.userLoginId, + schoolId = it.schoolId, + schoolName = it.schoolName, + schoolShortName = it.schoolShortName, + parentIds = it.parentIds, + studentIds = it.studentIds, + employeeIds = it.employeeIds, + error = it.error, + students = it.subjects + .filterIsInstance() + .map { registerSubject -> + RegisterStudent( + studentId = registerSubject.studentId, + studentName = registerSubject.studentName, + studentSecondName = registerSubject.studentSecondName, + studentSurname = registerSubject.studentSurname, + className = registerSubject.className, + classId = registerSubject.classId, + isParent = registerSubject.isParent, + semesters = registerSubject.semesters + .mapSemesters() + .mapToEntities(registerSubject.studentId), + ) + }, + ) + } + ) + } +) + +fun RegisterStudent.mapToStudentWithSemesters( + user: RegisterUser, + symbol: RegisterSymbol, + unit: RegisterUnit, + colors: List, +): StudentWithSemesters = StudentWithSemesters( + semesters = semesters, + student = Student( + email = user.login, // for compatibility + userName = symbol.userName, + userLoginId = unit.userLoginId, + isParent = isParent, + className = className, + classId = classId, + studentId = studentId, + symbol = symbol.symbol, + loginType = user.loginType.name, + schoolName = unit.schoolName, + schoolShortName = unit.schoolShortName, + schoolSymbol = unit.schoolId, + studentName = "$studentName $studentSurname", + loginMode = Sdk.Mode.SCRAPPER.name, + scrapperBaseUrl = user.baseUrl, + mobileBaseUrl = "", + certificateKey = "", + privateKey = "", + password = user.password, + isCurrent = false, + registrationDate = Instant.now(), + ).apply { + avatarColor = colors.random() + }, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt new file mode 100644 index 000000000..4aea33771 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt @@ -0,0 +1,43 @@ +package io.github.wulkanowy.data.pojos + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.scrapper.Scrapper + +data class RegisterUser( + val email: String, + val password: String, + val login: String, // may be the same as email + val baseUrl: String, + val loginType: Scrapper.LoginType, + val symbols: List, +) : java.io.Serializable + +data class RegisterSymbol( + val symbol: String, + val error: Throwable?, + val userName: String, + val schools: List, +) : java.io.Serializable + +data class RegisterUnit( + val userLoginId: Int, + val schoolId: String, + val schoolName: String, + val schoolShortName: String, + val parentIds: List, + val studentIds: List, + val employeeIds: List, + val error: Throwable?, + val students: List, +) : java.io.Serializable + +data class RegisterStudent( + val studentId: Int, + val studentName: String, + val studentSecondName: String, + val studentSurname: String, + val className: String, + val classId: Int, + val isParent: Boolean, + val semesters: List, +) : java.io.Serializable 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 f006b7d28..b1d1ba832 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 @@ -11,6 +11,8 @@ import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.mappers.mapToPojo +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.DispatchersProvider @@ -52,6 +54,14 @@ class StudentRepository @Inject constructor( sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol) .mapToEntities(password, appInfo.defaultColorsForAvatar) + suspend fun getUserSubjectsFromScrapper( + email: String, + password: String, + scrapperBaseUrl: String, + symbol: String + ): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) + .mapToPojo(password) + suspend fun getStudentsHybrid( email: String, password: String, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index 8f237e537..c17c92efd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -8,10 +8,11 @@ import android.os.Bundle import android.view.MenuItem import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE import androidx.fragment.app.commit import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.databinding.ActivityLoginBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment @@ -76,8 +77,8 @@ class LoginActivity : BaseActivity(), Logi openFragment(LoginSymbolFragment.newInstance(loginData)) } - fun navigateToStudentSelect(studentsWithSemesters: List) { - openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters)) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser)) } fun navigateToNotifications() { @@ -105,6 +106,8 @@ class LoginActivity : BaseActivity(), Logi } private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) { + supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE) + supportFragmentManager.commit { replace(R.id.loginContainer, fragment) setReorderingAllowed(true) 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 5d4743589..ae6c22492 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,4 +6,5 @@ data class LoginData( val login: String, val password: String, val baseUrl: 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 37dcb38b3..8c90623e1 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 @@ -8,7 +8,7 @@ import android.widget.ArrayAdapter import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseFragment @@ -327,8 +327,8 @@ class LoginAdvancedFragment : (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) } - override fun navigateToStudentSelect(studentsWithSemesters: List) { - (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun onResume() { 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 1b42c6c52..33a76e5f9 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 @@ -4,9 +4,15 @@ import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.scrapper.Scrapper +import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -142,19 +148,23 @@ class LoginAdvancedPresenter @Inject constructor( is Resource.Success -> { analytics.logEvent( "registration_form", - "success" to true, - "students" to it.data.size, - "error" to "No error" - ) - val loginData = LoginData( - login = view?.formUsernameValue.orEmpty().trim(), - password = view?.formPassValue.orEmpty().trim(), - baseUrl = view?.formHostValue.orEmpty().trim() - ) - when (it.data.size) { - 0 -> view?.navigateToSymbol(loginData) - else -> view?.navigateToStudentSelect(it.data) - } + "success" to true, + "students" to it.data.size, + "error" to "No error" + ) + val loginData = LoginData( + login = view?.formUsernameValue.orEmpty().trim(), + password = view?.formPassValue.orEmpty().trim(), + baseUrl = view?.formHostValue.orEmpty().trim(), + symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), + ) + when (it.data.size) { + 0 -> view?.navigateToSymbol(loginData) + else -> view?.navigateToStudentSelect( + loginData = loginData, + registerUser = it.data.toRegisterUser(loginData), + ) + } } is Resource.Error -> { analytics.logEvent( @@ -173,6 +183,58 @@ class LoginAdvancedPresenter @Inject constructor( }.launch("login") } + private fun List.toRegisterUser(loginData: LoginData) = RegisterUser( + email = loginData.login, + password = loginData.password, + login = loginData.login, + baseUrl = loginData.baseUrl, + loginType = firstOrNull()?.student?.loginType?.let( + Scrapper.LoginType::valueOf + ) ?: Scrapper.LoginType.AUTO, + symbols = this + .groupBy { students -> students.student.symbol } + .map { (symbol, students) -> + RegisterSymbol( + symbol = symbol, + error = null, + userName = "", + schools = students + .groupBy { student -> + Triple( + first = student.student.schoolSymbol, + second = student.student.userLoginId, + third = student.student.schoolShortName + ) + } + .map { (groupKey, students) -> + val (schoolId, loginId, schoolName) = groupKey + RegisterUnit( + students = students.map { + RegisterStudent( + studentId = it.student.studentId, + studentName = it.student.studentName, + studentSecondName = it.student.studentName, + studentSurname = it.student.studentName, + className = it.student.className, + classId = it.student.classId, + isParent = it.student.isParent, + semesters = it.semesters, + ) + }, + userLoginId = loginId, + schoolId = schoolId, + schoolName = schoolName, + schoolShortName = schoolName, + parentIds = listOf(), + studentIds = listOf(), + employeeIds = listOf(), + error = null + ) + } + ) + }, + ) + private suspend fun getStudentsAppropriatesToLoginType(): List { val email = view?.formUsernameValue.orEmpty() val password = view?.formPassValue.orEmpty() 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 f9b84f1ab..824fa0288 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.login.advanced import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData @@ -72,7 +73,7 @@ interface LoginAdvancedView : BaseView { fun navigateToSymbol(loginData: LoginData) - fun navigateToStudentSelect(studentsWithSemesters: List) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun setErrorPinRequired() 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 463e192de..a0e7608d6 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,7 +9,7 @@ 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.StudentWithSemesters +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 @@ -226,8 +226,8 @@ class LoginFormFragment : BaseFragment(R.layout.fragme (activity as? LoginActivity)?.navigateToSymbolFragment(loginData) } - override fun navigateToStudentSelect(studentsWithSemesters: List) { - (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun openAdvancedLogin() { 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 0acb0ea6d..8035ea0ad 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 @@ -93,7 +93,7 @@ class LoginFormPresenter @Inject constructor( if (!validateCredentials(email, password, host)) return resourceFlow { - studentRepository.getStudentsScrapper( + studentRepository.getUserSubjectsFromScrapper( email = email, password = password, scrapperBaseUrl = host, @@ -109,14 +109,14 @@ class LoginFormPresenter @Inject constructor( } } .onResourceSuccess { - when (it.size) { - 0 -> view?.navigateToSymbol(LoginData(email, password, host)) - else -> view?.navigateToStudentSelect(it) + val loginData = LoginData(email, password, host, symbol) + when (it.symbols.size) { + 0 -> view?.navigateToSymbol(loginData) + else -> view?.navigateToStudentSelect(loginData, it) } analytics.logEvent( "registration_form", "success" to true, - "students" to it.size, "scrapperBaseUrl" to host, "error" to "No error" ) @@ -134,7 +134,6 @@ class LoginFormPresenter @Inject constructor( analytics.logEvent( "registration_form", "success" to false, - "students" to -1, "scrapperBaseUrl" to host, "error" to it.message.ifNullOrBlank { "No message" } ) 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 8003975db..5a816fb32 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,6 +1,6 @@ package io.github.wulkanowy.ui.modules.login.form -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData @@ -60,7 +60,7 @@ interface LoginFormView : BaseView { fun navigateToSymbol(loginData: LoginData) - fun navigateToStudentSelect(studentsWithSemesters: List) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun openPrivacyPolicyPage() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index c046c2ff5..e6d131829 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -2,65 +2,182 @@ package io.github.wulkanowy.ui.modules.login.studentselect import android.annotation.SuppressLint import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil.ItemCallback +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.* import javax.inject.Inject +@SuppressLint("SetTextI18n") class LoginStudentSelectAdapter @Inject constructor() : - RecyclerView.Adapter() { + ListAdapter(Differ) { - private val checkedList = mutableMapOf() + override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal - var items = emptyList>() - set(value) { - field = value - checkedList.clear() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (LoginStudentSelectItemType.values()[viewType]) { + LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder( + ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false), + ) + LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder( + ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false) + ) + LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder( + ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false) + ) + LoginStudentSelectItemType.STUDENT -> StudentViewHolder( + ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false) + ) + LoginStudentSelectItemType.HELP -> HelpViewHolder( + ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false) + ) } + } - var onClickListener: (StudentWithSemesters, alreadySaved: Boolean) -> Unit = { _, _ -> } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader) + is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader) + is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader) + is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student) + is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help) + } + } - override fun getItemCount() = items.size + private class EmptySymbolsHeaderViewHolder( + private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding, + ) : RecyclerView.ViewHolder(binding.root) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( - ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) - - @SuppressLint("SetTextI18n") - override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { - val (studentAndSemesters, alreadySaved) = items[position] - val student = studentAndSemesters.student - val semesters = studentAndSemesters.semesters - val diary = semesters.maxByOrNull { it.semesterId } - - with(holder.binding) { - loginItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}" - loginItemSchool.text = student.schoolName - loginItemName.isEnabled = !alreadySaved - loginItemSchool.isEnabled = !alreadySaved - loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE - - with(loginItemCheck) { - isEnabled = !alreadySaved - keyListener = null - isChecked = checkedList[position] ?: false + fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) { + with(binding) { + loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f + root.setOnClickListener { item.onClick() } } + } + } - root.setOnClickListener { - onClickListener(studentAndSemesters, alreadySaved) + private class SymbolsHeaderViewHolder( + private val binding: ItemLoginStudentSelectHeaderSymbolBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.SymbolHeader) { + with(binding) { + loginStudentSelectHeaderSymbolValue.text = buildString { + append(root.context.getString(R.string.mobile_device_symbol)) + append(": ") + append(item.humanReadableName ?: item.symbol.symbol) + if (!item.humanReadableName.isNullOrBlank()) { + append(" (${item.symbol.symbol})") + } + } + loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName + loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank() + loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message + loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null + loginStudentSelectHeaderSymbolError.maxLines = when { + item.isErrorExpanded -> Int.MAX_VALUE + else -> 2 + } + + if (item.symbol.error != null) { + root.setOnClickListener { item.onClick(item.symbol) } + } else root.setOnClickListener(null) + } + } + } + + private class SchoolHeaderViewHolder( + private val binding: ItemLoginStudentSelectHeaderSchoolBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.SchoolHeader) { + with(binding) { + loginStudentSelectHeaderSchoolName.text = buildString { + append(item.unit.schoolName.trim()) + append(" (") + append(item.unit.schoolShortName) + append(")") + } + loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty() + loginStudentSelectHeaderSchoolError.text = item.unit.error?.message + loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null + loginStudentSelectHeaderSchoolError.maxLines = when { + item.isErrorExpanded -> Int.MAX_VALUE + else -> 2 + } + + if (item.unit.error != null) { + root.setOnClickListener { item.onClick(item.unit) } + } else root.setOnClickListener(null) + } + } + } + + private class StudentViewHolder( + private val binding: ItemLoginStudentSelectStudentBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.Student) { + val student = item.student + val semesters = student.semesters + val diary = semesters.maxByOrNull { it.semesterId } + + with(binding) { + loginItemName.text = "${student.studentName} ${student.studentSurname}" + loginItemName.isEnabled = item.isEnabled + loginItemSignedIn.text = if (!item.isEnabled) { + root.context.getString(R.string.login_signed_in) + } else diary?.diaryName with(loginItemCheck) { - if (isEnabled) { - isChecked = !isChecked - checkedList[position] = isChecked - } + keyListener = null + isEnabled = item.isEnabled + isChecked = item.isSelected || !item.isEnabled + } + + root.isEnabled = item.isEnabled + root.setOnClickListener { + item.onClick(item) } } } } - class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) : - RecyclerView.ViewHolder(binding.root) + private class HelpViewHolder( + private val binding: ItemLoginStudentSelectHelpBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LoginStudentSelectItem.Help) { + with(binding) { + loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible + loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() } + loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() } + loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() } + } + } + } + + private object Differ : ItemCallback() { + + override fun areItemsTheSame( + oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem + ): Boolean = when { + oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true + oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> { + oldItem.symbol == newItem.symbol + } + oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> { + oldItem.student == newItem.student + } + else -> oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem + ): Boolean = oldItem == newItem + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 03aced14e..169702151 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -2,17 +2,16 @@ package io.github.wulkanowy.ui.modules.login.studentselect import android.os.Bundle import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE import androidx.core.os.bundleOf -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser @@ -36,12 +35,23 @@ class LoginStudentSelectFragment : @Inject lateinit var preferencesRepository: PreferencesRepository - companion object { - const val ARG_STUDENTS = "STUDENTS" + private lateinit var symbolsNames: Array + private lateinit var symbolsValues: Array - fun newInstance(studentsWithSemesters: List) = + override val symbols: Map by lazy { + symbolsValues.zip(symbolsNames).toMap() + } + + companion object { + private const val ARG_LOGIN = "LOGIN" + private const val ARG_STUDENTS = "STUDENTS" + + fun newInstance(loginData: LoginData, registerUser: RegisterUser) = LoginStudentSelectFragment().apply { - arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters) + arguments = bundleOf( + ARG_LOGIN to loginData, + ARG_STUDENTS to registerUser, + ) } } @@ -49,34 +59,32 @@ class LoginStudentSelectFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLoginStudentSelectBinding.bind(view) + + symbolsNames = resources.getStringArray(R.array.symbols) + symbolsValues = resources.getStringArray(R.array.symbols_values) + presenter.onAttachView( view = this, - students = requireArguments().serializable(ARG_STUDENTS), + loginData = requireArguments().serializable(ARG_LOGIN), + registerUser = requireArguments().serializable(ARG_STUDENTS), ) } override fun initView() { (requireActivity() as LoginActivity).showActionBar(true) - loginAdapter.onClickListener = presenter::onItemSelected - with(binding) { loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } - loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } - loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } - - with(loginStudentSelectRecycler) { - layoutManager = LinearLayoutManager(context) - adapter = loginAdapter - } + loginStudentSelectRecycler.adapter = loginAdapter } } - override fun updateData(data: List>) { - with(loginAdapter) { - items = data - notifyDataSetChanged() - } + override fun updateData(data: List) { + loginAdapter.submitList(data) + } + + override fun navigateToSymbol(loginData: LoginData) { + (requireActivity() as LoginActivity).navigateToSymbolFragment(loginData) } override fun navigateToNext() { @@ -84,26 +92,17 @@ class LoginStudentSelectFragment : } override fun showProgress(show: Boolean) { - binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectProgress.isVisible = show } override fun showContent(show: Boolean) { - binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE + binding.loginStudentSelectContent.isVisible = show } override fun enableSignIn(enable: Boolean) { binding.loginStudentSelectSignIn.isEnabled = enable } - override fun showContact(show: Boolean) { - binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE - } - - override fun onDestroyView() { - presenter.onDetachView() - super.onDestroyView() - } - override fun openDiscordInvite() { context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) } @@ -124,4 +123,9 @@ class LoginStudentSelectFragment : ) ) } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt new file mode 100644 index 000000000..1edc8e7b4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectItem.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.ui.modules.login.studentselect + +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit + +sealed class LoginStudentSelectItem(val type: LoginStudentSelectItemType) { + + data class EmptySymbolsHeader( + val isExpanded: Boolean, + val onClick: () -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER) + + data class SymbolHeader( + val symbol: RegisterSymbol, + val humanReadableName: String?, + val isErrorExpanded: Boolean, + val onClick: (RegisterSymbol) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.SYMBOL_HEADER) + + data class SchoolHeader( + val unit: RegisterUnit, + val isErrorExpanded: Boolean, + val onClick: (RegisterUnit) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.SCHOOL_HEADER) + + data class Student( + val symbol: RegisterSymbol, + val unit: RegisterUnit, + val student: RegisterStudent, + val isEnabled: Boolean, + val isSelected: Boolean, + val onClick: (Student) -> Unit, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.STUDENT) + + data class Help( + val onEnterSymbolClick: () -> Unit, + val onContactUsClick: () -> Unit, + val onDiscordClick: () -> Unit, + val isSymbolButtonVisible: Boolean, + ) : LoginStudentSelectItem(LoginStudentSelectItemType.HELP) +} + +enum class LoginStudentSelectItemType { + EMPTY_SYMBOLS_HEADER, + SYMBOL_HEADER, + SCHOOL_HEADER, + STUDENT, + HELP, +} 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 5a40a6bc3..f94ea7b72 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 @@ -1,15 +1,23 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -19,18 +27,30 @@ class LoginStudentSelectPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, private val syncManager: SyncManager, - private val analytics: AnalyticsHelper + private val analytics: AnalyticsHelper, + private val appInfo: AppInfo, ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null - private val selectedStudents = mutableListOf() + private lateinit var registerUser: RegisterUser + private lateinit var loginData: LoginData - fun onAttachView(view: LoginStudentSelectView, students: List) { + private lateinit var students: List + private var isEmptySymbolsExpanded = false + private var expandedSymbolError: RegisterSymbol? = null + private var expandedSchoolError: RegisterUnit? = null + + private val selectedStudents = mutableListOf() + + fun onAttachView( + view: LoginStudentSelectView, + loginData: LoginData, + registerUser: RegisterUser, + ) { super.onAttachView(view) with(view) { initView() - showContact(false) enableSignIn(false) loginErrorHandler.onStudentDuplicate = { showMessage(it) @@ -38,50 +58,171 @@ class LoginStudentSelectPresenter @Inject constructor( } } - if (students.size == 1) registerStudents(students) - loadData(students) + this.loginData = loginData + this.registerUser = registerUser + loadData() } + private fun loadData() { + resetSelectedState() + + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + students = it.dataOrNull.orEmpty() + when (it) { + is Resource.Loading -> Timber.d("Login student select students load started") + is Resource.Success -> refreshItems() + is Resource.Error -> { + errorHandler.dispatch(it.error) + lastError = it.error + refreshItems() + } + } + }.launch() + } + + private fun createItems(): List = buildList { + val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } + val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } + + if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) { + add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol })) + } + + addAll(createNotEmptySymbolItems(notEmptySymbols, students)) + addAll(createEmptySymbolItems(emptySymbols, notEmptySymbols.isNotEmpty())) + + val helpItem = LoginStudentSelectItem.Help( + onEnterSymbolClick = ::onEnterSymbol, + onContactUsClick = ::onEmailClick, + onDiscordClick = ::onDiscordClick, + isSymbolButtonVisible = "login" !in loginData.baseUrl, + ) + add(helpItem) + } + + private fun createNotEmptySymbolItems( + notEmptySymbols: List, + students: List, + ) = buildList { + notEmptySymbols.forEach { registerSymbol -> + val symbolHeader = LoginStudentSelectItem.SymbolHeader( + symbol = registerSymbol, + humanReadableName = view?.symbols?.get(registerSymbol.symbol), + isErrorExpanded = expandedSymbolError == registerSymbol, + onClick = ::onSymbolItemClick, + ) + add(symbolHeader) + + registerSymbol.schools.forEach { registerUnit -> + val schoolHeader = LoginStudentSelectItem.SchoolHeader( + unit = registerUnit, + isErrorExpanded = expandedSchoolError == registerUnit, + onClick = ::onUnitItemClick, + ) + add(schoolHeader) + + registerUnit.students.forEach { + add(createStudentItem(it, registerSymbol, registerUnit, students)) + } + } + } + } + + private fun createStudentItem( + student: RegisterStudent, + symbol: RegisterSymbol, + school: RegisterUnit, + students: List, + ) = LoginStudentSelectItem.Student( + symbol = symbol, + unit = school, + student = student, + onClick = ::onItemSelected, + isEnabled = students.none { + it.student.email == registerUser.login + && it.student.symbol == symbol.symbol + && it.student.studentId == student.studentId + && it.student.schoolSymbol == school.schoolId + && it.student.classId == student.classId + }, + isSelected = selectedStudents + .filter { it.symbol.symbol == symbol.symbol } + .filter { it.unit.schoolId == school.schoolId } + .filter { it.student.studentId == student.studentId } + .filter { it.student.classId == student.classId } + .size == 1, + ) + + private fun createEmptySymbolItems( + emptySymbols: List, + isNotEmptySymbolsExist: Boolean, + ) = buildList { + val filteredEmptySymbols = emptySymbols.filter { + it.error !is AccountPermissionException + }.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() } + + if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) { + val emptyHeader = LoginStudentSelectItem.EmptySymbolsHeader( + isExpanded = isEmptySymbolsExpanded, + onClick = ::onEmptySymbolsToggle, + ) + add(emptyHeader) + if (isEmptySymbolsExpanded) { + filteredEmptySymbols.forEach { + add(createEmptySymbolItem(it)) + } + } + } + + if (filteredEmptySymbols.isNotEmpty() && !isNotEmptySymbolsExist) { + filteredEmptySymbols.forEach { + add(createEmptySymbolItem(it)) + } + } + } + + private fun createEmptySymbolItem(registerSymbol: RegisterSymbol) = + LoginStudentSelectItem.SymbolHeader( + symbol = registerSymbol, + humanReadableName = view?.symbols?.get(registerSymbol.symbol), + isErrorExpanded = expandedSymbolError == registerSymbol, + onClick = ::onSymbolItemClick, + ) + fun onSignIn() { registerStudents(selectedStudents) } - fun onItemSelected(studentWithSemester: StudentWithSemesters, alreadySaved: Boolean) { - if (alreadySaved) return + private fun onEmptySymbolsToggle() { + isEmptySymbolsExpanded = !isEmptySymbolsExpanded + + refreshItems() + } + + private fun onItemSelected(item: LoginStudentSelectItem.Student) { + if (!item.isEnabled) return selectedStudents - .removeAll { it == studentWithSemester } - .let { if (!it) selectedStudents.add(studentWithSemester) } + .removeAll { + it.student.studentId == item.student.studentId && + it.student.classId == item.student.classId && + it.unit.schoolId == item.unit.schoolId && + it.symbol.symbol == item.symbol.symbol + } + .let { if (!it) selectedStudents.add(item) } view?.enableSignIn(selectedStudents.isNotEmpty()) + refreshItems() } - private fun compareStudents(a: Student, b: Student): Boolean { - return a.email == b.email - && a.symbol == b.symbol - && a.studentId == b.studentId - && a.schoolSymbol == b.schoolSymbol - && a.classId == b.classId + private fun onSymbolItemClick(symbol: RegisterSymbol) { + expandedSymbolError = if (symbol != expandedSymbolError) symbol else null + refreshItems() } - private fun loadData(studentsWithSemesters: List) { - resetSelectedState() - - resourceFlow { studentRepository.getSavedStudents(false) }.onEach { - when (it) { - is Resource.Loading -> Timber.d("Login student select students load started") - is Resource.Success -> view?.updateData(studentsWithSemesters.map { studentWithSemesters -> - studentWithSemesters to it.data.any { item -> - compareStudents(studentWithSemesters.student, item.student) - } - }) - is Resource.Error -> { - errorHandler.dispatch(it.error) - lastError = it.error - view?.updateData(studentsWithSemesters.map { student -> student to false }) - } - } - }.launch() + private fun onUnitItemClick(unit: RegisterUnit) { + expandedSchoolError = if (unit != expandedSchoolError) unit else null + refreshItems() } private fun resetSelectedState() { @@ -89,7 +230,20 @@ class LoginStudentSelectPresenter @Inject constructor( view?.enableSignIn(false) } - private fun registerStudents(studentsWithSemesters: List) { + private fun refreshItems() { + view?.updateData(createItems()) + } + + private fun registerStudents(students: List) { + val studentsWithSemesters = students + .filterIsInstance().map { item -> + item.student.mapToStudentWithSemesters( + user = registerUser, + symbol = item.symbol, + unit = item.unit, + colors = appInfo.defaultColorsForAvatar, + ) + } resourceFlow { studentRepository.saveStudents(studentsWithSemesters) } .logResourceStatus("registration") .onEach { @@ -107,7 +261,6 @@ class LoginStudentSelectPresenter @Inject constructor( view?.apply { showProgress(false) showContent(true) - showContact(true) } lastError = it.error loginErrorHandler.dispatch(it.error) @@ -117,12 +270,22 @@ class LoginStudentSelectPresenter @Inject constructor( }.launch("register") } - fun onDiscordClick() { + private fun onEnterSymbol() { + view?.navigateToSymbol(loginData) + } + + private fun onDiscordClick() { view?.openDiscordInvite() } - fun onEmailClick() { - view?.openEmail(lastError?.message.ifNullOrBlank { "empty" }) + private fun onEmailClick() { + view?.openEmail(lastError?.message.ifNullOrBlank { + registerUser.symbols.flatMap { symbol -> + symbol.schools.map { it.error?.message } + symbol.error?.message + }.filterNotNull().distinct().joinToString("; ") { + it.take(46) + "..." + }.ifEmpty { "blank" } + }) } private fun logRegisterEvent( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index 8d403271b..39f312bf3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -1,13 +1,17 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData interface LoginStudentSelectView : BaseView { + val symbols: Map + fun initView() - fun updateData(data: List>) + fun updateData(data: List) + + fun navigateToSymbol(loginData: LoginData) fun navigateToNext() @@ -17,8 +21,6 @@ interface LoginStudentSelectView : BaseView { fun enableSignIn(enable: Boolean) - fun showContact(show: Boolean) - fun openDiscordInvite() fun openEmail(lastError: String) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index ab27ecf3f..67416cb63 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -12,7 +12,7 @@ import androidx.core.text.parseAsHtml import androidx.core.widget.doOnTextChanged import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment @@ -42,6 +42,8 @@ class LoginSymbolFragment : } } + override val symbolValue: String? get() = binding.loginSymbolName.text?.toString() + override val symbolNameError: CharSequence? get() = binding.loginSymbolNameLayout.error @@ -58,7 +60,7 @@ class LoginSymbolFragment : (requireActivity() as LoginActivity).showActionBar(true) with(binding) { - loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } + loginSymbolSignIn.setOnClickListener { presenter.attemptLogin() } loginSymbolFaq.setOnClickListener { presenter.onFaqClick() } loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } @@ -92,9 +94,13 @@ class LoginSymbolFragment : } override fun setErrorSymbolRequire() { - binding.loginSymbolNameLayout.apply { + setErrorSymbol(getString(R.string.error_field_required)) + } + + override fun setErrorSymbol(message: String) { + with(binding.loginSymbolNameLayout) { requestFocus() - error = getString(R.string.error_field_required) + error = message } } @@ -125,8 +131,8 @@ class LoginSymbolFragment : binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE } - override fun navigateToStudentSelect(studentsWithSemesters: List) { - (activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters) + override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) { + (activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser) } override fun onSaveInstanceState(outState: Bundle) { 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 691cd4481..a6ccd7a57 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 @@ -1,9 +1,12 @@ package io.github.wulkanowy.ui.modules.login.symbol import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -23,9 +26,14 @@ class LoginSymbolPresenter @Inject constructor( lateinit var loginData: LoginData + private var registerUser: RegisterUser? = null + fun onAttachView(view: LoginSymbolView, loginData: LoginData) { super.onAttachView(view) this.loginData = loginData + loginErrorHandler.onBadCredentials = { + view.setErrorSymbol(it.orEmpty()) + } with(view) { initView() showContact(false) @@ -39,20 +47,24 @@ class LoginSymbolPresenter @Inject constructor( view?.apply { if (symbolNameError != null) clearSymbolError() } } - fun attemptLogin(symbol: String) { - if (symbol.isBlank()) { + fun attemptLogin() { + if (view?.symbolValue.isNullOrBlank()) { view?.setErrorSymbolRequire() return } + loginData = loginData.copy( + symbol = view?.symbolValue?.getNormalizedSymbol(), + ) resourceFlow { - studentRepository.getStudentsScrapper( + studentRepository.getUserSubjectsFromScrapper( email = loginData.login, password = loginData.password, scrapperBaseUrl = loginData.baseUrl, - symbol = symbol, + symbol = view?.symbolValue.orEmpty(), ) }.onEach { + registerUser = it.dataOrNull when (it) { is Resource.Loading -> view?.run { Timber.i("Login with symbol started") @@ -61,7 +73,7 @@ class LoginSymbolPresenter @Inject constructor( showContent(false) } is Resource.Success -> { - when (it.data.size) { + when (it.data.symbols.size) { 0 -> { Timber.i("Login with symbol result: Empty student list") view?.run { @@ -71,15 +83,14 @@ class LoginSymbolPresenter @Inject constructor( } else -> { Timber.i("Login with symbol result: Success") - view?.navigateToStudentSelect(requireNotNull(it.data)) + view?.navigateToStudentSelect(loginData, requireNotNull(it.data)) } } analytics.logEvent( "registration_symbol", "success" to true, - "students" to it.data.size, "scrapperBaseUrl" to loginData.baseUrl, - "symbol" to symbol, + "symbol" to view?.symbolValue, "error" to "No error" ) } @@ -90,7 +101,7 @@ class LoginSymbolPresenter @Inject constructor( "success" to false, "students" to -1, "scrapperBaseUrl" to loginData.baseUrl, - "symbol" to symbol, + "symbol" to view?.symbolValue, "error" to it.error.message.ifNullOrBlank { "No message" } ) loginErrorHandler.dispatch(it.error) @@ -111,6 +122,12 @@ class LoginSymbolPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { "empty" }) + view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { + registerUser?.symbols?.flatMap { symbol -> + symbol.schools.map { it.error?.message } + symbol.error?.message + }?.filterNotNull()?.distinct()?.joinToString(";") { + it.take(46) + "..." + } ?: "blank" + }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 527895b77..6b62d1f7f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.ui.modules.login.symbol -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.login.LoginData interface LoginSymbolView : BaseView { + val symbolValue: String? + val symbolNameError: CharSequence? fun initView() @@ -15,6 +18,8 @@ interface LoginSymbolView : BaseView { fun setErrorSymbolRequire() + fun setErrorSymbol(message: String) + fun clearSymbolError() fun clearAndFocusSymbol() @@ -27,7 +32,7 @@ interface LoginSymbolView : BaseView { fun showContent(show: Boolean) - fun navigateToStudentSelect(studentsWithSemesters: List) + fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) fun showContact(show: Boolean) diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index bf5431164..c47b9ae35 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -3,92 +3,14 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> - - + android:orientation="vertical" + tools:context=".ui.modules.login.studentselect.LoginStudentSelectFragment"> - - - - - - - - - - - - - - - - + tools:itemCount="33" + tools:listitem="@layout/item_login_student_select_student" /> + + diff --git a/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml b/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml new file mode 100644 index 000000000..be0fd905c --- /dev/null +++ b/app/src/main/res/layout/item_login_student_select_empty_symbol_header.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_login_student_select_header_school.xml b/app/src/main/res/layout/item_login_student_select_header_school.xml new file mode 100644 index 000000000..30a8bbf0b --- /dev/null +++ b/app/src/main/res/layout/item_login_student_select_header_school.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_login_student_select_header_symbol.xml b/app/src/main/res/layout/item_login_student_select_header_symbol.xml new file mode 100644 index 000000000..cc1bf709d --- /dev/null +++ b/app/src/main/res/layout/item_login_student_select_header_symbol.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_login_student_select_help.xml b/app/src/main/res/layout/item_login_student_select_help.xml new file mode 100644 index 000000000..b6d81c7cd --- /dev/null +++ b/app/src/main/res/layout/item_login_student_select_help.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_login_student_select.xml b/app/src/main/res/layout/item_login_student_select_student.xml similarity index 71% rename from app/src/main/res/layout/item_login_student_select.xml rename to app/src/main/res/layout/item_login_student_select_student.xml index 1003636fc..d071b1bbf 100644 --- a/app/src/main/res/layout/item_login_student_select.xml +++ b/app/src/main/res/layout/item_login_student_select_student.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" - android:minHeight="72dp" + android:minHeight="56dp" android:paddingTop="8dp" android:paddingBottom="8dp" tools:context=".ui.modules.login.studentselect.LoginStudentSelectAdapter"> @@ -14,9 +14,10 @@ android:layout_width="32dp" android:layout_height="24dp" android:layout_centerVertical="true" - android:layout_marginStart="12dp" + android:layout_marginStart="32dp" android:layout_marginEnd="28dp" android:background="@android:color/transparent" + android:clickable="false" tools:text=" " /> - - + android:textSize="14sp" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26ab51efa..38997054f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,7 +55,7 @@ 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 → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen + 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 @@ -74,6 +74,9 @@ 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 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 9bcfb8b6c..bf2d9f2cc 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 @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.modules.login.form import io.github.wulkanowy.MainCoroutineRule -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.sdk.scrapper.Scrapper import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.mockk.* @@ -12,7 +12,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import java.io.IOException -import java.time.Instant class LoginFormPresenterTest { @@ -33,6 +32,15 @@ class LoginFormPresenterTest { private lateinit var presenter: LoginFormPresenter + private val registerUser = RegisterUser( + email = "", + password = "", + login = "", + baseUrl = "", + loginType = Scrapper.LoginType.AUTO, + symbols = listOf(), + ) + @Before fun setUp() { MockKAnnotations.init(this) @@ -104,32 +112,9 @@ class LoginFormPresenterTest { @Test fun loginTest() { - val studentTest = Student( - email = "test@", - password = "123", - scrapperBaseUrl = "https://fakelog.cf/?email", - loginType = "AUTO", - studentName = "", - schoolSymbol = "", - schoolName = "", - studentId = 0, - classId = 1, - isCurrent = false, - symbol = "", - registrationDate = Instant.now(), - className = "", - mobileBaseUrl = "", - privateKey = "", - certificateKey = "", - loginMode = "", - userLoginId = 0, - schoolShortName = "", - isParent = false, - userName = "" - ) - coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf( - StudentWithSemesters(studentTest, emptyList()) - ) + coEvery { + repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + } returns registerUser every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" @@ -146,7 +131,9 @@ class LoginFormPresenterTest { @Test fun loginEmptyTest() { - coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf() + coEvery { + repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + } returns registerUser every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" every { loginFormView.formHostValue } returns "https://fakelog.cf/?email" @@ -162,7 +149,9 @@ class LoginFormPresenterTest { @Test fun loginEmptyTwiceTest() { - coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf() + coEvery { + repository.getUserSubjectsFromScrapper(any(), any(), any(), any()) + } returns registerUser every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" every { loginFormView.formHostValue } returns "https://fakelog.cf/?email" @@ -180,7 +169,14 @@ class LoginFormPresenterTest { @Test fun loginErrorTest() { val testException = IOException("test") - coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } throws testException + coEvery { + repository.getUserSubjectsFromScrapper( + any(), + any(), + any(), + any() + ) + } throws testException every { loginFormView.formUsernameValue } returns "@" every { loginFormView.formPassValue } returns "123456" every { loginFormView.formHostValue } returns "https://fakelog.cf/?email" 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 a31ef5177..cf426a50b 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 @@ -1,18 +1,22 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.MainCoroutineRule -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.RegisterStudent +import io.github.wulkanowy.data.pojos.RegisterSymbol +import io.github.wulkanowy.data.pojos.RegisterUnit +import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.sdk.scrapper.Scrapper import io.github.wulkanowy.services.sync.SyncManager +import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.mockk.* import io.mockk.impl.annotations.MockK import org.junit.Before import org.junit.Rule import org.junit.Test -import java.time.Instant class LoginStudentSelectPresenterTest { @@ -22,7 +26,7 @@ class LoginStudentSelectPresenterTest { @MockK(relaxed = true) lateinit var errorHandler: LoginErrorHandler - @MockK(relaxed = true) + @MockK lateinit var loginStudentSelectView: LoginStudentSelectView @MockK @@ -34,33 +38,55 @@ class LoginStudentSelectPresenterTest { @MockK(relaxed = true) lateinit var syncManager: SyncManager + private val appInfo = AppInfo() + private lateinit var presenter: LoginStudentSelectPresenter - private val testStudent by lazy { - Student( - email = "test", - password = "test123", - scrapperBaseUrl = "https://fakelog.cf", - loginType = "AUTO", - symbol = "", - isCurrent = false, - studentId = 0, - schoolName = "", - schoolSymbol = "", - classId = 1, - studentName = "", - registrationDate = Instant.now(), - className = "", - loginMode = "", - certificateKey = "", - privateKey = "", - mobileBaseUrl = "", - schoolShortName = "", - userLoginId = 1, - isParent = false, - userName = "" - ) - } + private val loginData = LoginData( + login = "", + password = "", + baseUrl = "", + symbol = null, + ) + + private val subject = RegisterStudent( + studentId = 0, + studentName = "", + studentSecondName = "", + studentSurname = "", + className = "", + classId = 0, + isParent = false, + semesters = listOf(), + ) + + private val school = RegisterUnit( + userLoginId = 0, + schoolId = "", + schoolName = "", + schoolShortName = "", + parentIds = listOf(), + studentIds = listOf(), + employeeIds = listOf(), + error = null, + students = listOf(subject) + ) + + private val symbol = RegisterSymbol( + symbol = "", + error = null, + userName = "", + schools = listOf(school), + ) + + private val registerUser = RegisterUser( + email = "", + password = "", + login = "", + baseUrl = "", + loginType = Scrapper.LoginType.AUTO, + symbols = listOf(symbol), + ) private val testException by lazy { RuntimeException("Problem") } @@ -69,30 +95,44 @@ class LoginStudentSelectPresenterTest { MockKAnnotations.init(this) clearMocks(studentRepository, loginStudentSelectView) + + coEvery { studentRepository.getSavedStudents(false) } returns emptyList() + every { loginStudentSelectView.initView() } just Runs - every { loginStudentSelectView.showContact(any()) } just Runs + every { loginStudentSelectView.symbols } returns emptyMap() + every { loginStudentSelectView.enableSignIn(any()) } just Runs every { loginStudentSelectView.showProgress(any()) } just Runs every { loginStudentSelectView.showContent(any()) } just Runs - presenter = LoginStudentSelectPresenter(studentRepository, errorHandler, syncManager, analytics) - presenter.onAttachView(loginStudentSelectView, emptyList()) + presenter = LoginStudentSelectPresenter( + studentRepository = studentRepository, + loginErrorHandler = errorHandler, + syncManager = syncManager, + analytics = analytics, + appInfo = appInfo, + ) } @Test fun initViewTest() { + presenter.onAttachView(loginStudentSelectView, loginData, registerUser) verify { loginStudentSelectView.initView() } } @Test fun onSelectedStudentTest() { - coEvery { - studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) - } just Runs + val itemsSlot = slot>() + every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs + presenter.onAttachView(loginStudentSelectView, loginData, registerUser) + + coEvery { studentRepository.saveStudents(any()) } just Runs every { loginStudentSelectView.navigateToNext() } just Runs - presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false) + itemsSlot.captured.filterIsInstance().first().let { + it.onClick(it) + } presenter.onSignIn() verify { loginStudentSelectView.showContent(false) } @@ -102,13 +142,15 @@ class LoginStudentSelectPresenterTest { @Test fun onSelectedStudentErrorTest() { - coEvery { - studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) - } throws testException + val itemsSlot = slot>() + every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs + presenter.onAttachView(loginStudentSelectView, loginData, registerUser) - coEvery { studentRepository.logoutStudent(testStudent) } just Runs + coEvery { studentRepository.saveStudents(any()) } throws testException - presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false) + itemsSlot.captured.filterIsInstance().first().let { + it.onClick(it) + } presenter.onSignIn() verify { loginStudentSelectView.showContent(false) } From b30b7c3318a9de656ba841af8315fc3a29bf5fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 1 Jan 2023 21:52:46 +0100 Subject: [PATCH 095/545] New Crowdin updates (#2068) --- app/src/main/res/values-cs/strings.xml | 12 +- .../res/values-da-rDK/preferences_values.xml | 65 ++ app/src/main/res/values-da-rDK/strings.xml | 728 ++++++++++++++++++ app/src/main/res/values-de/strings.xml | 16 +- app/src/main/res/values-es-rES/strings.xml | 12 +- app/src/main/res/values-pl/strings.xml | 12 +- app/src/main/res/values-ru/strings.xml | 12 +- app/src/main/res/values-sk/strings.xml | 14 +- app/src/main/res/values-uk/strings.xml | 12 +- 9 files changed, 873 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/values-da-rDK/preferences_values.xml create mode 100644 app/src/main/res/values-da-rDK/strings.xml diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index f41cb17f9..4a91cc852 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -55,7 +55,7 @@ Neplatný symbol Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Vybraný žák je už přihlášen - Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+ + Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli Variace deníku UONET+ na první přihlašovací obrazovce Vyberte žáky, kteří se mají do aplikace přihlásit Jiné možnosti V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí frekvencí, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení @@ -72,6 +72,14 @@ Obnovit Žák je už přihlášen Standardní + Jiná místa vyhledávání + Nebyli nalezeni žádní aktivní žáci + Zadejte jiný symbol + + Povolit oznámení + Povolit upozornění, abyste nezmeškali zprávu od učitele nebo o nové známce + Přeskočit + Zapnout Manažer účtů Přihlásit se @@ -485,6 +493,8 @@ Přítomnost na setkání Agenda + Místo + Téma Školní oznámení Žádná školní oznámení diff --git a/app/src/main/res/values-da-rDK/preferences_values.xml b/app/src/main/res/values-da-rDK/preferences_values.xml new file mode 100644 index 000000000..ac2b6e9e5 --- /dev/null +++ b/app/src/main/res/values-da-rDK/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-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml new file mode 100644 index 000000000..667231274 --- /dev/null +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -0,0 +1,728 @@ + + + + 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 + + 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 + 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 + + No lessons + 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 + 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 + + 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-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6107fbb96..f7b8e7c4d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -55,7 +55,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. - Das Symbol kann auf der Registerseite in Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilnegefunden werden.\n\nStellen Sie sicher, dass Sie die entsprechende Registervariante im Feld UONET+ Registervariante auf dem vorherigen Bildschirm festgelegt haben + 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 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 @@ -72,6 +72,14 @@ Wiederherstellen Student ist bereits angemeldet 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 Kundenbetreuer Anmelden @@ -275,7 +283,7 @@ Nachricht nicht vorhanden Sie müssen mindestens 1 Empfänger auswählen. Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein. - All mailboxes + Alle postfächer Nur ungelesen Nur mit Anhängen Lesen: %s @@ -299,7 +307,7 @@ %1$d ausgewählt Nachrichten gelöscht - Choose mailbox + Postfach auswählen Keine Informationen über Eintragen Punkte @@ -413,6 +421,8 @@ Teilnahme an einem Meeting Agenda + Place + Topic Schulankündigungen Keine schulankündigungen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 95a00a602..667231274 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -55,7 +55,7 @@ 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 → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen + 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 @@ -72,6 +72,14 @@ 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 @@ -413,6 +421,8 @@ Present at conference Agenda + Place + Topic School announcements No school announcements diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f612d826d..4891015d2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -55,7 +55,7 @@ Nieprawidłowy symbol Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+ Wybrany uczeń jest już zalogowany - Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika + Symbol można znaleźć na stronie dziennika w Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUpewnij się, że ustawiłeś odpowiednią odmianę dziennika w polu Odmiana dziennika UONET+ na pierwszym ekranie logowania Wybierz uczniów do zalogowania w aplikacji Inne opcje W tym trybie nie działa szczęśliwy numerek, uczeń na tle klasy, podsumowanie frekwencji, usprawiedliwianie nieobecności, lekcje zrealizowane, informacje o szkole i podgląd listy zarejestrowanych urządzeń @@ -72,6 +72,14 @@ Przywróć Uczeń jest już zalogowany Standardowa + Inne lokalizacje wyszukiwania + Nie znaleziono aktywnych uczniów + Wprowadź inny symbol + + Włącz powiadomienia + Włącz powiadomienia, aby nie przegapić wiadomości od nauczyciela lub nowej oceny + Pomiń + Włącz Menadżer kont Zaloguj się @@ -485,6 +493,8 @@ Obecność na zebraniu Agenda + Miejsce + Temat Ogłoszenia szkolne Brak ogłoszeń szkolnych diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 195371fd0..01e43183f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -55,7 +55,7 @@ Неверный symbol Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+ Данный ученик уже авторизован - Symbol можно найти на странице регистрации в  Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nУбедитесь, что вы выбрали соответствующий тип дневника в поле Тип дневника UONET+ на предыдущем экране + 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 Выберите учеников для авторизации в приложении Другие варианты В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств @@ -72,6 +72,14 @@ Восстановить Ученик уже авторизован Стандартный + 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 Менеджер аккаунтов Войти @@ -485,6 +493,8 @@ Присутствует на встрече Повестка дня + Place + Topic Объявления школы Нет школьных объявлений diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9fd06bcbe..4189c5349 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -35,7 +35,7 @@ Email Prihlásenie, číslo PESEL alebo e-mail Heslo - Variácie denníka UONET+ + Variácia denníka UONET+ Mobile API Scraper Hybridné @@ -55,7 +55,7 @@ Neplatný symbol Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ Vybraný žiak už je prihlásený - Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUistite sa, že ste na predchádzajúcu obrazovke nastaviť správny variant denníka do poľa Variácie denníka UONET+ + Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUistite sa, že ste nastavili správny variant denníka v poli Variácia denníka UONET+ na prvej prihlasovacej obrazovke Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť Iné možnosti V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie frekvencií, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení @@ -72,6 +72,14 @@ Obnoviť Žiak je už prihlásený Štandardná + Iné miesta vyhľadávania + Neboli nájdení žiadni aktívni žiaci + Zadajte iný symbol + + Povoliť oznámenia + Povoliť oznámenia, aby ste nezmeškali správu od učiteľa alebo o novej známke + Preskočiť + Zapnúť Manažér účtov Prihlásiť sa @@ -485,6 +493,8 @@ Prítomnosť na stretnutí Agenda + Miesto + Téma Školské oznámenia Žiadne školské oznámenia diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2d3bf3724..99c340650 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -55,7 +55,7 @@ Неправильний symbol Студента не знайдено. Перевірте symbol та обранний тип щоденника UONET+ Цього учня вже авторизовано - Symbol можна знайти на сторінці реєстрації в  Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nПереконайтесь, що ви встановили відповідний тип щоденника в полі Тип щоденника UONET+ на попередньому екрані + 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 Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності та уроків, інформація про школу та список зареєстрованих пристроїв @@ -72,6 +72,14 @@ Відновити Учня вже авторизовано Стандартний + 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 Змінити облікові записи Увійти @@ -485,6 +493,8 @@ Присутність на зустрічі Порядок денний + Place + Topic Оголошення школи Немає шкільних оголошень From f4c6e0ad1b3a2149ebe9f822710845b553916b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 1 Jan 2023 21:57:39 +0100 Subject: [PATCH 096/545] Version 1.9.0 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b13f8da35..ce9039dfa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 118 - versionName "1.8.3" + versionCode 119 + versionName "1.9.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,7 +162,7 @@ play { track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS userFraction = 0.10d - updatePriority = 5 + updatePriority = 1 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:a3b97edd48" + implementation "io.github.wulkanowy:sdk:1.9.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 5a47ddc7e..9e24f9d9e 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,10 @@ -Wersja 1.8.3 +Wersja 1.9.0 -- naprawiliśmy logowanie dla użytkowników systemu Resman Rzeszów -- dodaliśmy wsparcie dla nowej platformy z Tomaszowa Mazowieckiego -- poprawiliśmy dopasowywanie skrzynek pocztowych do uczniów -- naprawiliśmy literówkę w tytule wiadomości z szablonem usprawiedliwienia +- dodaliśmy obsługę Androida 13 (w tym ikona aplikacji obsługująca Material You) +- przerobiliśmy ekran wyboru ucznia przy pierwszym logowaniu +- naprawiliśmy usuwanie wiadomości w niektórych przypadkach +- naprawiliśmy błąd występujący przy resecie hasła +- naprawiliśmy literówkę w tytule domyślnej treści wiadomości usprawiedliwiania +- naprawiliśmy nazwę aplikacji przy ustawionym w telefonie jezyku francuskim Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 1c9860091aef9901f79a75ba7e88f7912f324927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 07:45:25 +0000 Subject: [PATCH 097/545] Bump robolectric from 4.9.1 to 4.9.2 (#2093) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ce9039dfa..3477092a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -264,7 +264,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.9.1' + testImplementation 'org.robolectric:robolectric:4.9.2' testImplementation "androidx.test:runner:1.5.1" testImplementation "androidx.test.ext:junit:1.1.4" testImplementation "androidx.test:core:1.5.0" From 377e0c3a0dd4158fa38a51a44dfcb8a072e02a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 5 Jan 2023 22:42:35 +0100 Subject: [PATCH 098/545] Fix school name text wrap in dashboard and student details (#2095) --- app/src/main/res/layout/fragment_account_details.xml | 8 ++++++-- app/src/main/res/layout/item_dashboard_account.xml | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/layout/fragment_account_details.xml b/app/src/main/res/layout/fragment_account_details.xml index af9564b5e..0c904c07d 100644 --- a/app/src/main/res/layout/fragment_account_details.xml +++ b/app/src/main/res/layout/fragment_account_details.xml @@ -111,9 +111,11 @@ + tools:text="Szkoła Wulkanowego" /> - \ No newline at end of file + From 5161fdd543277aecef54ee6b14fdf935c314ec75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 5 Jan 2023 22:47:53 +0100 Subject: [PATCH 099/545] Add missing info to student selection email reports (#2096) --- app/build.gradle | 2 +- .../LoginStudentSelectPresenter.kt | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3477092a3..8bf52bc12 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.0" + implementation "io.github.wulkanowy:sdk:1.9.1-SNAPSHOT" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 f94ea7b72..9148b6ab9 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 @@ -280,11 +280,24 @@ class LoginStudentSelectPresenter @Inject constructor( private fun onEmailClick() { view?.openEmail(lastError?.message.ifNullOrBlank { - registerUser.symbols.flatMap { symbol -> - symbol.schools.map { it.error?.message } + symbol.error?.message - }.filterNotNull().distinct().joinToString("; ") { - it.take(46) + "..." - }.ifEmpty { "blank" } + loginData.baseUrl + "/" + loginData.symbol + "\n" + registerUser.symbols.filterNot { + it.error is AccountPermissionException && it.symbol != loginData.symbol + }.joinToString(";\n") { symbol -> + buildString { + append(" -") + append(symbol.symbol) + append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})") + if (symbol.schools.isNotEmpty()) { + append(": ") + } + append(symbol.schools.joinToString(", ") { unit -> + buildString { + append(unit.schoolShortName) + append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})") + } + }) + } + } }) } From 3eb74da9458d6db334317c9f9e8906d0985dcdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 5 Jan 2023 23:01:38 +0100 Subject: [PATCH 100/545] Version 1.9.1 --- 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 8bf52bc12..fff19ebe3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 119 - versionName "1.9.0" + versionCode 120 + versionName "1.9.1" 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 = 1 + userFraction = 0.50d + updatePriority = 2 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.1-SNAPSHOT" + implementation "io.github.wulkanowy:sdk:1.9.1" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 9e24f9d9e..76c6a0cb2 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 1.9.0 +Wersja 1.9.1 - dodaliśmy obsługę Androida 13 (w tym ikona aplikacji obsługująca Material You) - przerobiliśmy ekran wyboru ucznia przy pierwszym logowaniu From 368028c6f4a3a7a3868a5769b546d1297aaf3e9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 14:11:53 +0000 Subject: [PATCH 101/545] Bump kotlin_version from 1.7.21 to 1.8.0 (#2092) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 87e201acb..31252efac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.7.21' + kotlin_version = '1.8.0' about_libraries = '10.5.2' hilt_version = "2.44.2" } From 2293e8c1e6b7f6a75dd230e2491c5045763b31bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:17:17 +0000 Subject: [PATCH 102/545] Bump huawei-publish-gradle-plugin from 1.3.4 to 1.3.5 (#2101) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 31252efac..8d04bf035 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { classpath 'com.huawei.agconnect:agcp:1.7.3.302' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" - classpath "ru.cian:huawei-publish-gradle-plugin:1.3.4" + classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" From 84812fb048814e033d3693304ea7efb460ab97d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:17:38 +0000 Subject: [PATCH 103/545] Bump hianalytics from 6.9.0.301 to 6.9.1.200 (#2100) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index fff19ebe3..13b36ac72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.gms:play-services-ads:21.4.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.9.0.301' + hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 0306e381305663cce7a7e6eac88b986ee2de7278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:17:58 +0000 Subject: [PATCH 104/545] Bump runner from 1.5.1 to 1.5.2 (#2099) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 13b36ac72..614297723 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -265,7 +265,7 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation 'org.robolectric:robolectric:4.9.2' - testImplementation "androidx.test:runner:1.5.1" + testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.4" testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.room:room-testing:$room" From af8bb53c1769097acd4f9d2052bfffaf9a3a55f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:28:02 +0000 Subject: [PATCH 105/545] Bump junit from 1.1.4 to 1.1.5 (#2098) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 614297723..aaa4b6afc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -266,7 +266,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.9.2' testImplementation "androidx.test:runner:1.5.2" - testImplementation "androidx.test.ext:junit:1.1.4" + testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.room:room-testing:$room" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" From e1bffabf1063f1ea67009b5bbe14c3669f50644c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 14 Jan 2023 15:48:58 +0100 Subject: [PATCH 106/545] Fix marking message as read (#2102) --- app/build.gradle | 2 +- .../github/wulkanowy/data/repositories/MessageRepository.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index aaa4b6afc..067a8a473 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.1" + implementation "io.github.wulkanowy:sdk:1.9.2-SNAPSHOT" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 6dfc3ab73..53d9beadd 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 @@ -103,7 +103,10 @@ class MessageRepository @Inject constructor( messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, fetch = { - sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead) + sdk.init(student).getMessageDetails( + messageKey = it!!.message.messageGlobalKey, + markAsRead = message.unread && markAsRead, + ) }, saveFetchResult = { old, new -> checkNotNull(old) { "Fetched message no longer exist!" } From 89678c22767f333215d4bd440e70501f645c357b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:13:06 +0000 Subject: [PATCH 107/545] Bump agcp from 1.7.3.302 to 1.8.0.300 (#2108) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8d04bf035..2831fa736 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.huawei.agconnect:agcp:1.7.3.302' + classpath 'com.huawei.agconnect:agcp:1.8.0.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" From ac4a822930b1a72058407588f7e51b9179829968 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:13:34 +0000 Subject: [PATCH 108/545] Bump agconnect-crash from 1.7.3.302 to 1.8.0.300 (#2104) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 067a8a473..e15341f81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -251,7 +251,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:21.4.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.302' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From ef9e4b7ad960014484d444c6352a48b0befad014 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:14:15 +0000 Subject: [PATCH 109/545] Bump appcompat from 1.5.1 to 1.6.0 (#2107) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e15341f81..5219fed34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "androidx.core:core-ktx:1.9.0" implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.6.1" - implementation "androidx.appcompat:appcompat:1.5.1" + implementation "androidx.appcompat:appcompat:1.6.0" implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.annotation:annotation:1.5.0" From 95cf521f632160fd81996d3f7f6090c871f402ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:21:22 +0000 Subject: [PATCH 110/545] Bump gradle from 7.3.1 to 7.4.0 (#2106) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2831fa736..5abbe57d4 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.3.1' + classpath 'com.android.tools.build:gradle:7.4.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' classpath 'com.huawei.agconnect:agcp:1.8.0.300' From 6df3f22c7db756e771e8ea45898f3fcd24323d70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:30:53 +0000 Subject: [PATCH 111/545] Bump room from 2.4.3 to 2.5.0 (#2105) --- app/build.gradle | 2 +- .../main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5219fed34..33e80058d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,7 +179,7 @@ huaweiPublish { ext { work_manager = "2.7.1" android_hilt = "1.0.0" - room = "2.4.3" + room = "2.5.0" chucker = "3.5.2" mockk = "1.13.3" coroutines = "1.6.4" diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 87b3e0b32..cfa7a72a2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.data.db.dao import androidx.room.* -import androidx.room.OnConflictStrategy.ABORT import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters @@ -11,7 +10,7 @@ import javax.inject.Singleton @Dao abstract class StudentDao { - @Insert(onConflict = ABORT) + @Insert(onConflict = OnConflictStrategy.ABORT) abstract suspend fun insertAll(student: List): List @Delete From 32d6b4a7a6cc4c83c378aa132d16bcede347418c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 14 Jan 2023 17:06:47 +0100 Subject: [PATCH 112/545] Add menu order settings (#1924) --- .../repositories/PreferencesRepository.kt | 19 +- .../wulkanowy/ui/modules/Destination.kt | 30 ++- .../modules/conference/ConferenceFragment.kt | 10 +- .../modules/conference/ConferencePresenter.kt | 7 + .../ui/modules/conference/ConferenceView.kt | 2 + .../wulkanowy/ui/modules/exam/ExamFragment.kt | 14 +- .../ui/modules/exam/ExamPresenter.kt | 13 ++ .../wulkanowy/ui/modules/exam/ExamView.kt | 2 + .../ui/modules/homework/HomeworkFragment.kt | 10 +- .../ui/modules/homework/HomeworkPresenter.kt | 15 +- .../ui/modules/homework/HomeworkView.kt | 2 + .../luckynumber/LuckyNumberFragment.kt | 10 +- .../luckynumber/LuckyNumberPresenter.kt | 5 + .../ui/modules/luckynumber/LuckyNumberView.kt | 2 + .../wulkanowy/ui/modules/main/MainActivity.kt | 35 +-- .../ui/modules/main/MainPresenter.kt | 17 +- .../wulkanowy/ui/modules/main/MainView.kt | 7 +- .../ui/modules/message/MessageFragment.kt | 20 +- .../ui/modules/message/MessagePresenter.kt | 8 + .../ui/modules/message/MessageView.kt | 4 + .../modules/message/tab/MessageTabFragment.kt | 4 + .../message/tab/MessageTabPresenter.kt | 8 + .../mobiledevice/MobileDeviceFragment.kt | 10 +- .../mobiledevice/MobileDevicePresenter.kt | 6 + .../modules/mobiledevice/MobileDeviceView.kt | 2 + .../wulkanowy/ui/modules/more/MoreAdapter.kt | 14 +- .../wulkanowy/ui/modules/more/MoreFragment.kt | 88 +------- .../wulkanowy/ui/modules/more/MoreItem.kt | 12 + .../ui/modules/more/MorePresenter.kt | 58 +++-- .../wulkanowy/ui/modules/more/MoreView.kt | 44 +--- .../wulkanowy/ui/modules/note/NoteFragment.kt | 10 +- .../ui/modules/note/NotePresenter.kt | 6 + .../wulkanowy/ui/modules/note/NoteView.kt | 2 + .../appearance/menuorder/AppMenuItem.kt | 206 ++++++++++++++++++ .../menuorder/MenuItemMoveCallback.kt | 44 ++++ .../appearance/menuorder/MenuOrderAdapter.kt | 58 +++++ .../MenuOrderDividerItemDecoration.kt | 50 +++++ .../appearance/menuorder/MenuOrderFragment.kt | 101 +++++++++ .../appearance/menuorder/MenuOrderItem.kt | 6 + .../menuorder/MenuOrderPresenter.kt | 64 ++++++ .../appearance/menuorder/MenuOrderView.kt | 16 ++ .../main/res/drawable/ic_menu_order_drag.xml | 10 + .../main/res/layout/fragment_menu_order.xml | 12 + app/src/main/res/layout/item_menu_order.xml | 46 ++++ app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 8 + .../res/xml/scheme_preferences_appearance.xml | 14 +- .../ui/modules/main/MainPresenterTest.kt | 2 +- 48 files changed, 918 insertions(+), 216 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt create mode 100644 app/src/main/res/drawable/ic_menu_order_drag.xml create mode 100644 app/src/main/res/layout/fragment_menu_order.xml create mode 100644 app/src/main/res/layout/item_menu_order.xml 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 afc262868..29a65a96d 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 @@ -10,6 +10,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.ui.modules.dashboard.DashboardItem 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 @@ -28,9 +29,6 @@ class PreferencesRepository @Inject constructor( private val json: Json, ) { - val startMenuIndex: Int - get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt() - val isShowPresent: Boolean get() = getBoolean( R.string.pref_key_attendance_present, @@ -315,6 +313,20 @@ class PreferencesRepository @Inject constructor( putBoolean(context.getString(R.string.pref_key_ads_enabled), value) } + var appMenuItemOrder: List + get() { + val value = sharedPref.getString(PREF_KEY_APP_MENU_ITEM_ORDER, null) + ?: return AppMenuItem.defaultAppMenuItemList + + return json.decodeFromString(value) + } + set(value) = sharedPref.edit { + putString( + PREF_KEY_APP_MENU_ITEM_ORDER, + json.encodeToString(value) + ) + } + var installationId: String get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } @@ -341,6 +353,7 @@ class PreferencesRepository @Inject constructor( sharedPref.getBoolean(id, context.resources.getBoolean(default)) private companion object { + private const val PREF_KEY_APP_MENU_ITEM_ORDER = "app_menu_item_order" private const val PREF_KEY_INSTALLATION_ID = "installation_id" private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index 561419a05..958be5a72 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -10,10 +10,12 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.message.MessageFragment +import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment +import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment +import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import kotlinx.serialization.Serializable import java.time.LocalDate @@ -39,10 +41,12 @@ sealed class Destination { NOTE(Note), CONFERENCE(Conference), SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), - SCHOOL(School), - LUCKY_NUMBER(More), + SCHOOL_AND_TEACHERS(SchoolAndTeachers), + LUCKY_NUMBER(LuckyNumber), MORE(More), - MESSAGE(Message); + MESSAGE(Message), + MOBILE_DEVICE(MobileDevice), + SETTINGS(Settings); } @Serializable @@ -103,9 +107,9 @@ sealed class Destination { } @Serializable - object School : Destination() { - override val destinationType get() = Type.SCHOOL - override val destinationFragment get() = SchoolFragment.newInstance() + object SchoolAndTeachers : Destination() { + override val destinationType get() = Type.SCHOOL_AND_TEACHERS + override val destinationFragment get() = SchoolAndTeachersFragment.newInstance() } @Serializable @@ -125,4 +129,16 @@ sealed class Destination { override val destinationType get() = Type.MESSAGE override val destinationFragment get() = MessageFragment.newInstance() } + + @Serializable + object MobileDevice : Destination() { + override val destinationType get() = Type.MOBILE_DEVICE + override val destinationFragment get() = MobileDeviceFragment.newInstance() + } + + @Serializable + object Settings : Destination() { + override val destinationType get() = Type.SETTINGS + override val destinationFragment get() = SettingsFragment.newInstance() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt index b9642b1c7..0cd3150c7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt @@ -16,7 +16,7 @@ import javax.inject.Inject @AndroidEntryPoint class ConferenceFragment : BaseFragment(R.layout.fragment_conference), - ConferenceView, MainView.TitledView { + ConferenceView, MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: ConferencePresenter @@ -109,6 +109,14 @@ class ConferenceFragment : BaseFragment(R.layout.frag (activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference)) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.conferenceRecycler.smoothScrollToPosition(0) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt index f53648930..1178c7200 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -96,4 +96,11 @@ class ConferencePresenter @Inject constructor( .onResourceError(errorHandler::dispatch) .launch() } + + fun onFragmentReselected() { + Timber.i("Conference is reselected") + if (view?.isViewEmpty == false) { + view?.resetView() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt index 4f73394df..3299a1f08 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt @@ -28,4 +28,6 @@ interface ConferenceView : BaseView { fun showContent(show: Boolean) fun openConferenceDialog(conference: Conference) + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index ddd0e4a19..3d42bd00e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -2,9 +2,7 @@ package io.github.wulkanowy.ui.modules.exam import android.os.Bundle import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -20,7 +18,7 @@ import javax.inject.Inject @AndroidEntryPoint class ExamFragment : BaseFragment(R.layout.fragment_exam), ExamView, - MainView.TitledView { + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: ExamPresenter @@ -126,6 +124,14 @@ class ExamFragment : BaseFragment(R.layout.fragment_exam), (activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam)) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun resetView() { + binding.examRecycler.smoothScrollToPosition(0) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 99b0bcb87..858140728 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -175,4 +175,17 @@ class ExamPresenter @Inject constructor( ) } } + + fun onViewReselected() { + Timber.i("Exam view is reselected") + + baseDate = now().nextOrSameSchoolDay + + if (currentDate != baseDate) { + reloadView(baseDate) + loadData() + } else if (view?.isViewEmpty == false) { + view?.resetView() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt index 45b9e788c..677fac40e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamView.kt @@ -34,4 +34,6 @@ interface ExamView : BaseView { fun showPreButton(show: Boolean) fun showExamDialog(exam: Exam) + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index d4eaade2c..9d5130e40 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -21,7 +21,7 @@ import javax.inject.Inject @AndroidEntryPoint class HomeworkFragment : BaseFragment(R.layout.fragment_homework), - HomeworkView, MainView.TitledView { + HomeworkView, MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: HomeworkPresenter @@ -133,6 +133,14 @@ class HomeworkFragment : BaseFragment(R.layout.fragment (activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog()) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun resetView() { + binding.homeworkRecycler.smoothScrollToPosition(0) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index 2ac552b41..6b263e26d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -177,8 +177,21 @@ class HomeworkPresenter @Inject constructor( showNextButton(!currentDate.plusDays(7).isHolidays) updateNavigationWeek( "${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM") + currentDate.sunday.toFormattedString("dd.MM") ) } } + + fun onViewReselected() { + Timber.i("Homework view is reselected") + + baseDate = LocalDate.now().nextOrSameSchoolDay + + if (currentDate != baseDate) { + reloadView(baseDate) + loadData() + } else if (view?.isViewEmpty == false) { + view?.resetView() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt index 7c05ab865..56ba6c89e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkView.kt @@ -36,4 +36,6 @@ interface HomeworkView : BaseView { fun showHomeworkDialog(homework: Homework) fun showAddHomeworkDialog() + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt index 0a73fe15d..a6c75c1d9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt @@ -18,7 +18,7 @@ import javax.inject.Inject @AndroidEntryPoint class LuckyNumberFragment : BaseFragment(R.layout.fragment_lucky_number), LuckyNumberView, - MainView.TitledView { + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: LuckyNumberPresenter @@ -86,6 +86,14 @@ class LuckyNumberFragment : (activity as? MainActivity)?.pushView(LuckyNumberHistoryFragment.newInstance()) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onViewReselected() + } + + override fun popView() { + (activity as? MainActivity)?.popView() + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 6f5c8e740..2039624b1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -99,4 +99,9 @@ class LuckyNumberPresenter @Inject constructor( fun onDetailsClick() { view?.showErrorDetailsDialog(lastError) } + + fun onViewReselected() { + Timber.i("Luckynumber view is reselected") + view?.popView() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt index 0c05a1566..3d34ebc8f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt @@ -26,4 +26,6 @@ interface LuckyNumberView : BaseView { fun showContent(show: Boolean) fun openLuckyNumberHistory() + + fun popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index d332ee350..510923766 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -27,6 +27,7 @@ import io.github.wulkanowy.databinding.DialogAdsConsentBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog +import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem import io.github.wulkanowy.utils.* import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -124,13 +125,20 @@ class MainActivity : BaseActivity(), MainVie return true } - override fun initView(startMenuIndex: Int, rootDestinations: List) { + override fun initView( + startMenuIndex: Int, + rootAppMenuItems: List, + rootUpdatedDestinations: List + ) { initializeToolbar() - initializeBottomNavigation(startMenuIndex) - initializeNavController(startMenuIndex, rootDestinations) + initializeBottomNavigation(startMenuIndex, rootAppMenuItems) + initializeNavController(startMenuIndex, rootUpdatedDestinations) } - private fun initializeNavController(startMenuIndex: Int, rootDestinations: List) { + private fun initializeNavController( + startMenuIndex: Int, + rootUpdatedDestinations: List + ) { with(navController) { setOnViewChangeListener { destinationView -> presenter.onViewChange(destinationView) @@ -140,7 +148,7 @@ class MainActivity : BaseActivity(), MainVie ) } fragmentHideStrategy = HIDE - rootFragments = rootDestinations.map { it.destinationFragment } + rootFragments = rootUpdatedDestinations.map { it.destinationFragment } initialize(startMenuIndex, savedInstanceState) } @@ -156,17 +164,16 @@ class MainActivity : BaseActivity(), MainVie } } - private fun initializeBottomNavigation(startMenuIndex: Int) { + private fun initializeBottomNavigation( + startMenuIndex: Int, + rootAppMenuItems: List + ) { with(binding.mainBottomNav) { with(menu) { - add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title) - .setIcon(R.drawable.ic_main_dashboard) - add(Menu.NONE, 1, Menu.NONE, R.string.grade_title) - .setIcon(R.drawable.ic_main_grade) - add(Menu.NONE, 2, Menu.NONE, R.string.attendance_title) - .setIcon(R.drawable.ic_main_attendance) - add(Menu.NONE, 3, Menu.NONE, R.string.timetable_title) - .setIcon(R.drawable.ic_main_timetable) + rootAppMenuItems.forEachIndexed { index, item -> + add(Menu.NONE, index, Menu.NONE, item.title) + .setIcon(item.icon) + } add(Menu.NONE, 4, Menu.NONE, R.string.more_title) .setIcon(R.drawable.ic_main_more) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 458e966d4..d51cdac62 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -42,17 +42,16 @@ class MainPresenter @Inject constructor( private var studentsWitSemesters: List? = null - private val rootDestinationTypeList = listOf( - Destination.Type.DASHBOARD, - Destination.Type.GRADE, - Destination.Type.ATTENDANCE, - Destination.Type.TIMETABLE, - Destination.Type.MORE - ) + private val rootAppMenuItems = preferencesRepository.appMenuItemOrder + .sortedBy { it.order } + .take(4) + + private val rootDestinationTypeList = rootAppMenuItems.map { it.destinationType } + .plus(Destination.Type.MORE) private val Destination?.startMenuIndex get() = when { - this == null -> preferencesRepository.startMenuIndex + this == null -> 0 destinationType in rootDestinationTypeList -> { rootDestinationTypeList.indexOf(destinationType) } @@ -69,7 +68,7 @@ class MainPresenter @Inject constructor( if (it == initDestination?.destinationType) initDestination else it.defaultDestination } - view.initView(startMenuIndex, destinations) + view.initView(startMenuIndex, rootAppMenuItems, destinations) if (initDestination != null && startMenuIndex == 4) { view.openMoreDestination(initDestination) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 3d018e3d6..03f9641d0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem interface MainView : BaseView { @@ -15,7 +16,11 @@ interface MainView : BaseView { val currentStackSize: Int? - fun initView(startMenuIndex: Int, rootDestinations: List) + fun initView( + startMenuIndex: Int, + rootAppMenuItems: List, + rootUpdatedDestinations: List + ) fun switchMenuView(position: Int) 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 4607793c9..4317fb7fa 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 @@ -15,6 +15,7 @@ import io.github.wulkanowy.data.enums.MessageFolder.* import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment @@ -24,7 +25,7 @@ import javax.inject.Inject @AndroidEntryPoint class MessageFragment : BaseFragment(R.layout.fragment_message), - MessageView, MainView.TitledView { + MessageView, MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MessagePresenter @@ -123,8 +124,12 @@ class MessageFragment : BaseFragment(R.layout.fragment_m presenter.onChildViewShowNewMessage(show) } - fun onFragmentChanged() { - presenter.onFragmentChanged() + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun onFragmentChanged() { + if (::presenter.isInitialized) presenter.onFragmentChanged() } override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { @@ -139,10 +144,19 @@ class MessageFragment : BaseFragment(R.layout.fragment_m } } + override fun notifyChildParentReselected(index: Int) { + (pagerAdapter.getFragmentInstance(index) as? MessageTabFragment) + ?.onParentReselected() + } + override fun openSendMessage() { context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) } } + override fun popView() { + (activity as? MainActivity)?.popView() + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() 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 68bdc4b7c..cf6bad19e 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 @@ -39,6 +39,14 @@ class MessagePresenter @Inject constructor( view?.notifyChildrenFinishActionMode() } + fun onFragmentReselected() { + Timber.i("Message view is reselected") + view?.run { + popView() + notifyChildParentReselected(currentPageIndex) + } + } + fun onChildViewLoaded() { view?.apply { showContent(true) 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 e0cc5098c..def4a2751 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 @@ -20,5 +20,9 @@ interface MessageView : BaseView { fun notifyChildrenFinishActionMode() + fun notifyChildParentReselected(index: Int) + fun openSendMessage() + + fun popView() } 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 c78ccc6ec..592cbd604 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 @@ -235,6 +235,10 @@ class MessageTabFragment : BaseFragment(R.layout.frag presenter.onParentFinishActionMode() } + fun onParentReselected() { + presenter.onParentReselected() + } + private fun onChipChecked(chip: CompoundButton, isChecked: Boolean) { when (chip.id) { R.id.chip_unread -> presenter.onUnreadFilterSelected(isChecked) 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 ea142db2b..ec92e9c20 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 @@ -83,6 +83,14 @@ class MessageTabPresenter @Inject constructor( view?.showActionMode(false) } + fun onParentReselected() { + view?.run { + if (!isViewEmpty) { + resetListPosition() + } + } + } + fun onDestroyActionMode() { isActionMode = false messagesToDelete.clear() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt index f8e367c57..1afe773cf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceFragment.kt @@ -22,7 +22,7 @@ import javax.inject.Inject @AndroidEntryPoint class MobileDeviceFragment : BaseFragment(R.layout.fragment_mobile_device), MobileDeviceView, - MainView.TitledView { + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: MobileDevicePresenter @@ -135,6 +135,14 @@ class MobileDeviceFragment : (activity as? MainActivity)?.showDialogFragment(MobileDeviceTokenDialog.newInstance()) } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.mobileDevicesRecycler.smoothScrollToPosition(0) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index 36a720e53..56785dbf4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -129,4 +129,10 @@ class MobileDevicePresenter @Inject constructor( .onResourceError(errorHandler::dispatch) .launch("unregister") } + + fun onFragmentReselected() { + if (view?.isViewEmpty == false) { + view?.resetView() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt index b94646a7b..973cb1234 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDeviceView.kt @@ -32,4 +32,6 @@ interface MobileDeviceView : BaseView { fun setErrorDetails(message: String) fun showTokenDialog() + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt index 70587b0cf..697eab330 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreAdapter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.more -import android.graphics.drawable.Drawable import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView @@ -9,9 +8,9 @@ import javax.inject.Inject class MoreAdapter @Inject constructor() : RecyclerView.Adapter() { - var items = emptyList>() + var items = emptyList() - var onClickListener: (name: String) -> Unit = {} + var onClickListener: (moreItem: MoreItem) -> Unit = {} override fun getItemCount() = items.size @@ -20,13 +19,14 @@ class MoreAdapter @Inject constructor() : RecyclerView.Adapter(R.layout.fragment_more), override val titleStringId: Int get() = R.string.more_title - override val messagesRes: Pair? - get() = context?.run { getString(R.string.message_title) to getCompatDrawable(R.drawable.ic_more_messages) } - - override val homeworkRes: Pair? - get() = context?.run { getString(R.string.homework_title) to getCompatDrawable(R.drawable.ic_more_homework) } - - override val noteRes: Pair? - get() = context?.run { getString(R.string.note_title) to getCompatDrawable(R.drawable.ic_more_note) } - - override val conferencesRes: Pair? - get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) } - - override val schoolAnnouncementRes: Pair? - get() = context?.run { getString(R.string.school_announcement_title) to getCompatDrawable(R.drawable.ic_all_about) } - - override val schoolAndTeachersRes: Pair? - get() = context?.run { getString(R.string.schoolandteachers_title) to getCompatDrawable((R.drawable.ic_more_schoolandteachers)) } - - override val mobileDevicesRes: Pair? - get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) } - - override val settingsRes: Pair? - get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) } - - override val examRes: Pair? - get() = context?.run { getString(R.string.exam_title) to getCompatDrawable(R.drawable.ic_main_exam) } - - override val luckyNumberRes: Pair? - get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentMoreBinding.bind(view) @@ -94,57 +54,21 @@ class MoreFragment : BaseFragment(R.layout.fragment_more), ?.onFragmentChanged() } - override fun updateData(data: List>) { + override fun updateData(data: List) { with(moreAdapter) { items = data notifyDataSetChanged() } } - override fun openMessagesView() { - (activity as? MainActivity)?.pushView(MessageFragment.newInstance()) - } - - override fun openHomeworkView() { - (activity as? MainActivity)?.pushView(HomeworkFragment.newInstance()) - } - - override fun openNoteView() { - (activity as? MainActivity)?.pushView(NoteFragment.newInstance()) - } - - override fun openSchoolAnnouncementView() { - (activity as? MainActivity)?.pushView(SchoolAnnouncementFragment.newInstance()) - } - - override fun openConferencesView() { - (activity as? MainActivity)?.pushView(ConferenceFragment.newInstance()) - } - - override fun openSchoolAndTeachersView() { - (activity as? MainActivity)?.pushView(SchoolAndTeachersFragment.newInstance()) - } - - override fun openMobileDevicesView() { - (activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance()) - } - - override fun openSettingsView() { - (activity as? MainActivity)?.pushView(SettingsFragment.newInstance()) - } - - override fun openExamView() { - (activity as? MainActivity)?.pushView(ExamFragment.newInstance()) - } - - override fun openLuckyNumberView() { - (activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance()) - } - override fun popView(depth: Int) { (activity as? MainActivity)?.popView(depth) } + override fun openView(destination: Destination) { + (activity as? MainActivity)?.pushView(destination.destinationFragment) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt new file mode 100644 index 000000000..d544f041c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreItem.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.more + +import io.github.wulkanowy.ui.modules.Destination + +data class MoreItem( + + val icon: Int, + + val title: Int, + + val destination: Destination +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 92551d6e9..0ebaf4c7b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -1,16 +1,24 @@ package io.github.wulkanowy.ui.modules.more +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 +import io.github.wulkanowy.ui.modules.Destination import timber.log.Timber import javax.inject.Inject class MorePresenter @Inject constructor( errorHandler: ErrorHandler, - studentRepository: StudentRepository + studentRepository: StudentRepository, + preferencesRepository: PreferencesRepository ) : BasePresenter(errorHandler, studentRepository) { + private val moreAppMenuItem = preferencesRepository.appMenuItemOrder + .sortedBy { it.order } + .drop(4) + override fun onAttachView(view: MoreView) { super.onAttachView(view) view.initView() @@ -18,22 +26,10 @@ class MorePresenter @Inject constructor( loadData() } - fun onItemSelected(title: String) { - Timber.i("Select more item \"${title}\"") - view?.run { - when (title) { - messagesRes?.first -> openMessagesView() - examRes?.first -> openExamView() - homeworkRes?.first -> openHomeworkView() - noteRes?.first -> openNoteView() - conferencesRes?.first -> openConferencesView() - schoolAnnouncementRes?.first -> openSchoolAnnouncementView() - schoolAndTeachersRes?.first -> openSchoolAndTeachersView() - mobileDevicesRes?.first -> openMobileDevicesView() - settingsRes?.first -> openSettingsView() - luckyNumberRes?.first -> openLuckyNumberView() - } - } + fun onItemSelected(moreItem: MoreItem) { + Timber.i("Select more item \"${moreItem.destination.destinationType}\"") + + view?.openView(moreItem.destination) } fun onViewReselected() { @@ -43,19 +39,21 @@ class MorePresenter @Inject constructor( private fun loadData() { Timber.i("Load items for more view") - view?.run { - updateData(listOfNotNull( - messagesRes, - examRes, - homeworkRes, - noteRes, - luckyNumberRes, - conferencesRes, - schoolAnnouncementRes, - schoolAndTeachersRes, - mobileDevicesRes, - settingsRes - )) + val moreItems = moreAppMenuItem.map { + MoreItem( + icon = it.icon, + title = it.title, + destination = it.destinationType.defaultDestination + ) } + .plus( + MoreItem( + icon = R.drawable.ic_more_settings, + title = R.string.settings_title, + destination = Destination.Settings + ) + ) + + view?.updateData(moreItems) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt index cb895de28..fbca97edc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt @@ -1,53 +1,15 @@ package io.github.wulkanowy.ui.modules.more -import android.graphics.drawable.Drawable import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.Destination interface MoreView : BaseView { - val messagesRes: Pair? - - val homeworkRes: Pair? - - val noteRes: Pair? - - val conferencesRes: Pair? - - val schoolAnnouncementRes: Pair? - - val schoolAndTeachersRes: Pair? - - val mobileDevicesRes: Pair? - - val settingsRes: Pair? - - val examRes: Pair? - - val luckyNumberRes: Pair? - fun initView() - fun updateData(data: List>) - - fun openSettingsView() + fun updateData(data: List) fun popView(depth: Int) - fun openMessagesView() - - fun openHomeworkView() - - fun openNoteView() - - fun openSchoolAnnouncementView() - - fun openConferencesView() - - fun openSchoolAndTeachersView() - - fun openMobileDevicesView() - - fun openExamView() - - fun openLuckyNumberView() + fun openView(destination: Destination) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt index dd6223448..cb54b3840 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteFragment.kt @@ -18,7 +18,7 @@ import javax.inject.Inject @AndroidEntryPoint class NoteFragment : BaseFragment(R.layout.fragment_note), NoteView, - MainView.TitledView { + MainView.TitledView, MainView.MainChildView { @Inject lateinit var presenter: NotePresenter @@ -112,6 +112,14 @@ class NoteFragment : BaseFragment(R.layout.fragment_note), binding.noteSwipe.isRefreshing = show } + override fun onFragmentReselected() { + if (::presenter.isInitialized) presenter.onFragmentReselected() + } + + override fun resetView() { + binding.noteRecycler.smoothScrollToPosition(0) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 440565e11..62ad347fa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -121,4 +121,10 @@ class NotePresenter @Inject constructor( } .launch("update_note") } + + fun onFragmentReselected() { + if (view?.isViewEmpty == false) { + view?.resetView() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt index 9fc0be94c..b813b3159 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteView.kt @@ -30,4 +30,6 @@ interface NoteView : BaseView { fun showRefresh(show: Boolean) fun showNoteDialog(note: Note) + + fun resetView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt new file mode 100644 index 000000000..f522913ac --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/AppMenuItem.kt @@ -0,0 +1,206 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.Destination +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +sealed class AppMenuItem { + + companion object { + val defaultAppMenuItemList = setOf( + DashboardAppMenuItem(), + GradeAppMenuItem(), + TimetableAppMenuItem(), + AttendanceAppMenuItem(), + ExamsAppMenuItem(), + HomeworkAppMenuItem(), + NoteAppMenuItem(), + LuckyNumberAppMenuItem(), + SchoolAnnouncementsAppMenuItem(), + SchoolAndTeachersAppMenuItem(), + MobileDevicesAppMenuItem(), + ConferenceAppMenuItem(), + MessageAppMenuItem() + ).sortedBy { it.order } + } + + // https://youtrack.jetbrains.com/issue/KT-38958 + abstract var order: Int + + abstract val icon: Int + + abstract val title: Int + + abstract val destinationType: Destination.Type + + @Serializable + data class DashboardAppMenuItem(override var order: Int = 0) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_dashboard + + @Transient + override val title = R.string.dashboard_title + + @Transient + override val destinationType = Destination.Type.DASHBOARD + } + + @Serializable + data class GradeAppMenuItem(override var order: Int = 1) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_grade + + @Transient + override val title = R.string.grade_title + + @Transient + override val destinationType = Destination.Type.GRADE + } + + @Serializable + data class AttendanceAppMenuItem(override var order: Int = 2) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_attendance + + @Transient + override val title = R.string.attendance_title + + @Transient + override val destinationType = Destination.Type.ATTENDANCE + } + + @Serializable + data class TimetableAppMenuItem(override var order: Int = 3) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_timetable + + @Transient + override val title = R.string.timetable_title + + @Transient + override val destinationType = Destination.Type.TIMETABLE + } + + @Serializable + data class MessageAppMenuItem(override var order: Int = 4) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_messages + + @Transient + override val title = R.string.message_title + + @Transient + override val destinationType = Destination.Type.MESSAGE + } + + @Serializable + data class ExamsAppMenuItem(override var order: Int = 5) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_main_exam + + @Transient + override val title = R.string.exam_title + + @Transient + override val destinationType = Destination.Type.EXAM + } + + @Serializable + data class HomeworkAppMenuItem(override var order: Int = 6) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_homework + + @Transient + override val title = R.string.homework_title + + @Transient + override val destinationType = Destination.Type.HOMEWORK + } + + @Serializable + data class NoteAppMenuItem(override var order: Int = 7) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_note + + @Transient + override val title = R.string.note_title + + @Transient + override val destinationType = Destination.Type.NOTE + } + + @Serializable + data class LuckyNumberAppMenuItem(override var order: Int = 8) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_lucky_number + + @Transient + override val title = R.string.lucky_number_title + + @Transient + override val destinationType = Destination.Type.LUCKY_NUMBER + } + + @Serializable + data class ConferenceAppMenuItem(override var order: Int = 9) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_conferences + + @Transient + override val title = R.string.conferences_title + + @Transient + override val destinationType = Destination.Type.CONFERENCE + } + + @Serializable + data class SchoolAnnouncementsAppMenuItem(override var order: Int = 10) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_all_about + + @Transient + override val title = R.string.school_announcement_title + + @Transient + override val destinationType = Destination.Type.SCHOOL_ANNOUNCEMENT + } + + @Serializable + data class SchoolAndTeachersAppMenuItem(override var order: Int = 11) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_schoolandteachers + + @Transient + override val title = R.string.schoolandteachers_title + + @Transient + override val destinationType = Destination.Type.SCHOOL_AND_TEACHERS + } + + @Serializable + data class MobileDevicesAppMenuItem(override var order: Int = 12) : AppMenuItem() { + + @Transient + override val icon = R.drawable.ic_more_mobile_devices + + @Transient + override val title = R.string.mobile_devices_title + + @Transient + override val destinationType = Destination.Type.MOBILE_DEVICE + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt new file mode 100644 index 000000000..49196fad2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuItemMoveCallback.kt @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import java.util.* + +class MenuItemMoveCallback( + private val menuOrderAdapter: MenuOrderAdapter, + private var onUserInteractionEndListener: (List) -> Unit = {} +) : ItemTouchHelper.Callback() { + + override fun isLongPressDragEnabled() = true + + override fun isItemViewSwipeEnabled() = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + //Not implemented + } + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val list = menuOrderAdapter.items.toMutableList() + + Collections.swap(list, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + + menuOrderAdapter.submitList(list) + return true + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + onUserInteractionEndListener(menuOrderAdapter.items.toList()) + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt new file mode 100644 index 000000000..6bdd2fe0c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderAdapter.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.databinding.ItemMenuOrderBinding +import javax.inject.Inject + +class MenuOrderAdapter @Inject constructor() : + RecyclerView.Adapter() { + + val items = mutableListOf() + + fun submitList(newItems: List) { + val diffResult = DiffUtil.calculateDiff(DiffCallback(newItems, items.toMutableList())) + + with(items) { + clear() + addAll(newItems) + } + + diffResult.dispatchUpdatesTo(this) + } + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemMenuOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position].appMenuItem + + with(holder.binding) { + menuOrderItemTitle.setText(item.title) + menuOrderItemIcon.setImageResource(item.icon) + } + } + + class ViewHolder(val binding: ItemMenuOrderBinding) : RecyclerView.ViewHolder(binding.root) + + private class DiffCallback( + private val oldList: List, + private val newList: List + ) : DiffUtil.Callback() { + + override fun getNewListSize() = newList.size + + override fun getOldListSize() = oldList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].appMenuItem.destinationType == newList[newItemPosition].appMenuItem.destinationType + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt new file mode 100644 index 000000000..21a7f5e80 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderDividerItemDecoration.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.ShapeDrawable +import android.view.View +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.forEach +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.utils.getThemeAttrColor + +class MenuOrderDividerItemDecoration(private val context: Context) : + DividerItemDecoration(context, VERTICAL) { + + private val dividerDrawable = ShapeDrawable() + .apply { + DrawableCompat.setTint(this, context.getThemeAttrColor(R.attr.colorDivider)) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + canvas.save() + val dividerLeft = parent.paddingLeft + val dividerRight = parent.width - parent.paddingRight + + parent.forEach { + if (parent.getChildAdapterPosition(it) == 3) { + val params = it.layoutParams as RecyclerView.LayoutParams + val dividerTop = it.bottom + params.bottomMargin + val dividerBottom = dividerTop + dividerDrawable.intrinsicHeight + + dividerDrawable.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom + 30) + dividerDrawable.draw(canvas) + } + } + + canvas.restore() + } + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, + state: RecyclerView.State + ) { + if (parent.getChildAdapterPosition(view) == 3) { + outRect.bottom = dividerDrawable.intrinsicHeight + 30 + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt new file mode 100644 index 000000000..e08fc5dd3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderFragment.kt @@ -0,0 +1,101 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.activity.addCallback +import androidx.core.view.MenuProvider +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.SimpleItemAnimator +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.FragmentMenuOrderBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import javax.inject.Inject + + +@AndroidEntryPoint +class MenuOrderFragment : BaseFragment(R.layout.fragment_menu_order), + MenuOrderView, MainView.TitledView { + + @Inject + lateinit var presenter: MenuOrderPresenter + + @Inject + lateinit var menuOrderAdapter: MenuOrderAdapter + + override val titleStringId = R.string.menu_order_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentMenuOrderBinding.bind(view) + presenter.onAttachView(this) + } + + override fun initView() { + val itemTouchHelper = ItemTouchHelper( + MenuItemMoveCallback(menuOrderAdapter, presenter::onDragAndDropEnd) + ) + + itemTouchHelper.attachToRecyclerView(binding.menuOrderRecycler) + + with(binding.menuOrderRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = menuOrderAdapter + addItemDecoration(MenuOrderDividerItemDecoration(context)) + addItemDecoration(DividerItemDecoration(context)) + (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false + } + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + presenter.onBackSelected() + } + + initializeToolbar() + } + + private fun initializeToolbar() { + requireActivity().addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == android.R.id.home) { + presenter.onBackSelected() + return true + } + return false + } + + }, viewLifecycleOwner) + } + + override fun updateData(data: List) { + menuOrderAdapter.submitList(data) + } + + override fun restartApp() { + startActivity(MainActivity.getStartIntent(requireContext())) + requireActivity().finishAffinity() + } + + override fun popView() { + (activity as? MainActivity?)?.popView() + } + + override fun showRestartConfirmationDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.menu_order_confirm_title) + .setMessage(R.string.menu_order_confirm_content) + .setPositiveButton(R.string.menu_order_confirm_restart) { _, _ -> presenter.onConfirmRestart() } + .setNegativeButton(R.string.all_cancel) { _, _ -> presenter.onCancelRestart() } + .show() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt new file mode 100644 index 000000000..b82bc1bd2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderItem.kt @@ -0,0 +1,6 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +data class MenuOrderItem( + val appMenuItem: AppMenuItem, + val order: Int +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt new file mode 100644 index 000000000..1c90cc5e6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderPresenter.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +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 +import timber.log.Timber +import javax.inject.Inject + +class MenuOrderPresenter @Inject constructor( + studentRepository: StudentRepository, + errorHandler: ErrorHandler, + private val preferencesRepository: PreferencesRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var updatedMenuOrderItems = emptyList() + + override fun onAttachView(view: MenuOrderView) { + super.onAttachView(view) + view.initView() + Timber.i("Menu order view was initialized") + loadData() + } + + private fun loadData() { + val savedMenuItemList = (preferencesRepository.appMenuItemOrder) + .sortedBy { it.order } + .map { MenuOrderItem(it, it.order) } + + view?.updateData(savedMenuItemList) + } + + fun onDragAndDropEnd(list: List) { + val updatedList = list.mapIndexed { index, menuOrderItem -> + menuOrderItem.copy(order = index) + } + + updatedMenuOrderItems = updatedList + view?.updateData(updatedList) + } + + fun onBackSelected() { + if (updatedMenuOrderItems.isNotEmpty()) { + view?.showRestartConfirmationDialog() + } else { + view?.popView() + } + } + + fun onConfirmRestart() { + updatedMenuOrderItems.forEach { + it.appMenuItem.apply { + order = it.order + } + } + + preferencesRepository.appMenuItemOrder = updatedMenuOrderItems.map { it.appMenuItem } + view?.restartApp() + } + + fun onCancelRestart() { + view?.popView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt new file mode 100644 index 000000000..264e68ccb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/menuorder/MenuOrderView.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.ui.modules.settings.appearance.menuorder + +import io.github.wulkanowy.ui.base.BaseView + +interface MenuOrderView : BaseView { + + fun initView() + + fun updateData(data: List) + + fun restartApp() + + fun showRestartConfirmationDialog() + + fun popView() +} diff --git a/app/src/main/res/drawable/ic_menu_order_drag.xml b/app/src/main/res/drawable/ic_menu_order_drag.xml new file mode 100644 index 000000000..623c67d2e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_order_drag.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_menu_order.xml b/app/src/main/res/layout/fragment_menu_order.xml new file mode 100644 index 000000000..1773b45ac --- /dev/null +++ b/app/src/main/res/layout/fragment_menu_order.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/item_menu_order.xml b/app/src/main/res/layout/item_menu_order.xml new file mode 100644 index 000000000..84510d1ed --- /dev/null +++ b/app/src/main/res/layout/item_menu_order.xml @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 80a71bc71..739c1877f 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -40,4 +40,5 @@ ads_privacy_policy ads_consent_data_processing ads_over_eighteen + appearance_menu_order diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38997054f..d8fbe9b08 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,7 @@ Student info Dashboard Notifications center + Menu configuartion Semester %1$d, %2$d/%3$d @@ -595,6 +596,7 @@ Undo Change Add to calendar + Cancel No lessons Choose theme @@ -616,6 +618,8 @@ Grades color scheme Subjects sorting Language + Menu configuration + Set the order of functions in the menu Notifications Other Show notifications @@ -717,6 +721,10 @@ 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 No internet connection An error occurred. Check your device clock diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index b2da0287f..62216c760 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -20,23 +20,21 @@ app:key="@string/pref_key_app_theme" app:title="@string/pref_view_app_theme" app:useSimpleSummaryProvider="true" /> - + app:key="@string/pref_key_menu_order" + app:summary="@string/pref_view_menu_order_summary" + app:title="@string/pref_view_menu_order_title" /> diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 6cfab1995..460c8385f 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -46,7 +46,7 @@ class MainPresenterTest { MockKAnnotations.init(this) clearMocks(mainView) - every { mainView.initView(any(), any()) } just Runs + every { mainView.initView(any(), any(), any()) } just Runs presenter = MainPresenter( errorHandler = errorHandler, studentRepository = studentRepository, From f7d12670e7903dbaffd4dc3f2b13e4eecd351679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Jan 2023 17:54:58 +0000 Subject: [PATCH 113/545] Bump firebase-bom from 31.1.1 to 31.2.0 (#2111) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 33e80058d..d4e939909 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.8.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.1.1') + playImplementation platform('com.google.firebase:firebase-bom:31.2.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 277c3c7f0b641bf3c48ecfe49cff525fd45bc6bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Jan 2023 17:55:16 +0000 Subject: [PATCH 114/545] Bump google-services from 4.3.14 to 4.3.15 (#2110) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5abbe57d4..dfe7f927c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath 'com.android.tools.build:gradle:7.4.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.14' + classpath 'com.google.gms:google-services:4.3.15' classpath 'com.huawei.agconnect:agcp:1.8.0.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' classpath "com.github.triplet.gradle:play-publisher:3.6.0" From d1b198222de672b13316edf6e95b95b2cb793efa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:52:36 +0000 Subject: [PATCH 115/545] Bump material from 1.7.0 to 1.8.0 (#2113) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d4e939909..afc0d8715 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.7.0" + implementation "com.google.android.material:material:1.8.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 22dd16d278feb2db9a61f21ad3c2ebf16d33436e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:53:30 +0000 Subject: [PATCH 116/545] Bump mockk from 1.13.3 to 1.13.4 (#2112) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index afc0d8715..30f75c2a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.5.0" chucker = "3.5.2" - mockk = "1.13.3" + mockk = "1.13.4" coroutines = "1.6.4" } From 6596f3226b50787737059ac61ae7afaedb188b55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:39:48 +0000 Subject: [PATCH 117/545] Bump com.google.firebase:firebase-bom from 31.2.0 to 31.2.1 (#2123) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 30f75c2a8..f279c8004 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.8.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.2.0') + playImplementation platform('com.google.firebase:firebase-bom:31.2.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 2760318f3d0744bbf713892a36200c6bc19dd25c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:40:06 +0000 Subject: [PATCH 118/545] Bump androidx.appcompat:appcompat from 1.6.0 to 1.6.1 (#2122) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f279c8004..b96f38afd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { implementation "androidx.core:core-ktx:1.9.0" implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.6.1" - implementation "androidx.appcompat:appcompat:1.6.0" + implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.annotation:annotation:1.5.0" From 87b8989dc81c355fd08b3d9862e46b0a959b87d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:40:26 +0000 Subject: [PATCH 119/545] Bump com.google.firebase:firebase-crashlytics-gradle from 2.9.2 to 2.9.4 (#2121) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dfe7f927c..633554c37 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.8.0.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730" From af6d5c3063510cf49d9d5e68aa37bb5ae26c3b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:44:16 +0000 Subject: [PATCH 120/545] Bump work_manager from 2.7.1 to 2.8.0 (#2119) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b96f38afd..e24718fec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ huaweiPublish { } ext { - work_manager = "2.7.1" + work_manager = "2.8.0" android_hilt = "1.0.0" room = "2.5.0" chucker = "3.5.2" From 4d237d3672ae7535b15738f2b6aa8991d1a3e2cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:44:37 +0000 Subject: [PATCH 121/545] Bump com.android.tools.build:gradle from 7.4.0 to 7.4.1 (#2115) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 633554c37..50f80406f 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.0' + classpath 'com.android.tools.build:gradle:7.4.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.8.0.300' From 6f819bcb8084ce9948c8ed49c91c047b515e8d1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:44:57 +0000 Subject: [PATCH 122/545] Bump com.google.android.gms:play-services-ads from 21.4.0 to 21.5.0 (#2116) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e24718fec..edf5c7cc4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' 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:21.4.0' + playImplementation 'com.google.android.gms:play-services-ads:21.5.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.0.300' From d21e4afad2f0aacd934eb9dba53dc83406120593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:46:31 +0000 Subject: [PATCH 123/545] Bump com.android.tools:desugar_jdk_libs from 1.1.8 to 2.0.2 (#2118) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index edf5c7cc4..5d6c1c78f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,7 +188,7 @@ ext { dependencies { implementation "io.github.wulkanowy:sdk:1.9.2-SNAPSHOT" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" From 78ae23df684132a71c1362a573bcbae21335cecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:57:25 +0000 Subject: [PATCH 124/545] Bump kotlin_version from 1.8.0 to 1.8.10 (#2117) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 50f80406f..059540feb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.8.0' + kotlin_version = '1.8.10' about_libraries = '10.5.2' hilt_version = "2.44.2" } From ac446e4f91ab95bef194aec766805f7b65a7955b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Feb 2023 09:09:18 +0000 Subject: [PATCH 125/545] Bump hilt_version from 2.44.2 to 2.45 (#2120) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 059540feb..8dc23844e 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.8.10' about_libraries = '10.5.2' - hilt_version = "2.44.2" + hilt_version = "2.45" } repositories { mavenCentral() From ea26a6c1c900c88d39bb84476e6ffa1578363396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 20:57:36 +0000 Subject: [PATCH 126/545] Bump com.huawei.agconnect:agconnect-crash from 1.8.0.300 to 1.8.1.300 (#2136) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5d6c1c78f..e9461c26f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -251,7 +251,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:21.5.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.0.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.1.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 811f839949071cdcc7bfa61cbea7313568264e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 20:58:33 +0000 Subject: [PATCH 127/545] Bump androidx.test.ext:junit from 1.1.4 to 1.1.5 (#2135) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e9461c26f..e6f3acf97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -274,7 +274,7 @@ dependencies { androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test:runner:1.5.1" - androidTestImplementation "androidx.test.ext:junit:1.1.4" + androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" } From 0c2fd1d2db55c748aecf16dbf559c9b7ac7c991a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 20:58:55 +0000 Subject: [PATCH 128/545] Bump androidx.annotation:annotation from 1.5.0 to 1.6.0 (#2134) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e6f3acf97..729efc126 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { implementation "androidx.activity:activity-ktx:1.6.1" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.5" - implementation "androidx.annotation:annotation:1.5.0" + implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.recyclerview:recyclerview:1.2.1" From 3d2816874999d7df25f4ce1e4d0adc486ccbdde8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 20:59:36 +0000 Subject: [PATCH 129/545] Bump com.huawei.agconnect:agcp from 1.8.0.300 to 1.8.1.300 (#2131) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8dc23844e..ee597b0c3 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.4.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.8.0.300' + classpath 'com.huawei.agconnect:agcp:1.8.1.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" From 531c7592b2d3d43506d8c0bd1de5be2077570556 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:00:51 +0000 Subject: [PATCH 130/545] Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2132) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 729efc126..49685eeeb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.9.0" From 63bd5f95cb3fb8e46bdd6e0c3a07871d5f3e5caa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:02:04 +0000 Subject: [PATCH 131/545] Bump about_libraries from 10.5.2 to 10.6.1 (#2130) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ee597b0c3..33f5be3c7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.8.10' - about_libraries = '10.5.2' + about_libraries = '10.6.1' hilt_version = "2.45" } repositories { From 3e09a1dcee5d4a711081be4d5351c682ad403555 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:14:08 +0000 Subject: [PATCH 132/545] Bump com.fredporciuncula:flow-preferences from 1.8.0 to 1.9.0 (#2126) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 49685eeeb..1525b21c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -239,7 +239,7 @@ dependencies { implementation "io.coil-kt:coil:2.2.2" implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' - implementation 'com.fredporciuncula:flow-preferences:1.8.0' + implementation 'com.fredporciuncula:flow-preferences:1.9.0' implementation 'org.apache.commons:commons-text:1.10.0' playImplementation platform('com.google.firebase:firebase-bom:31.2.1') From 5b0e47b1c5fead141ef34da28a439dde349d7120 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:14:29 +0000 Subject: [PATCH 133/545] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2127) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 33f5be3c7..eed6655ad 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.5" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730" + 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 9ae2ffe7aecd8bacb615a5fa900cbc42eb9afd2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:17:58 +0000 Subject: [PATCH 134/545] Bump androidx.test:runner from 1.5.1 to 1.5.2 (#2133) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1525b21c4..1732d6e64 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -273,7 +273,7 @@ dependencies { kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" androidTestImplementation "androidx.test:core:1.5.0" - androidTestImplementation "androidx.test:runner:1.5.1" + androidTestImplementation "androidx.test:runner:1.5.2" androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" From 6ddaeb99da22c8b33ad944487a6efbf6044c3dd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:29:48 +0000 Subject: [PATCH 135/545] Bump com.google.firebase:firebase-bom from 31.2.1 to 31.2.2 (#2125) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1732d6e64..a470dae88 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.2.1') + playImplementation platform('com.google.firebase:firebase-bom:31.2.2') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 12a54278fc3aef03bba4afcf4f63ba67765f39dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 1 Mar 2023 22:53:42 +0100 Subject: [PATCH 136/545] New Crowdin updates (#2109) --- app/src/main/res/values-cs/strings.xml | 8 ++++ app/src/main/res/values-da-rDK/strings.xml | 8 ++++ app/src/main/res/values-de/strings.xml | 8 ++++ app/src/main/res/values-es-rES/strings.xml | 8 ++++ app/src/main/res/values-pl/strings.xml | 8 ++++ app/src/main/res/values-ru/strings.xml | 46 +++++++++++++--------- app/src/main/res/values-sk/strings.xml | 8 ++++ app/src/main/res/values-uk/strings.xml | 30 ++++++++------ 8 files changed, 94 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 4a91cc852..47f7707f5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -26,6 +26,7 @@ Informace o žáku Domů Centrum oznámení + Menu configuartion Semestr %1$d, %2$d/%3$d @@ -678,6 +679,7 @@ Vrátit Změnit Přidat do kalendáře + Cancel Žádné lekce Vybrat motiv @@ -699,6 +701,8 @@ Známky barevné schéma Třídění předmětů Jazyk + Menu configuration + Set the order of functions in the menu Oznámení Jiné Zobrazit oznámení @@ -800,6 +804,10 @@ Aktualizace byla stažena. Restartovat Aktualizace selhala! Wulkanowy nemusí fungovat správně. Zvažte aktualizaci + + Application restart + The application must restart for the changes to be saved + Restart Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 667231274..3875b3d98 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -26,6 +26,7 @@ Student info Dashboard Notifications center + Menu configuartion Semester %1$d, %2$d/%3$d @@ -590,6 +591,7 @@ Undo Change Add to calendar + Cancel No lessons Choose theme @@ -611,6 +613,8 @@ Grades color scheme Subjects sorting Language + Menu configuration + Set the order of functions in the menu Notifications Other Show notifications @@ -712,6 +716,10 @@ 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 No internet connection An error occurred. Check your device clock diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f7b8e7c4d..c03181e48 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -26,6 +26,7 @@ Schülerinfo Übersicht Benachrichtigungszentrum + Menu configuartion Semester %1$d, %2$d/%3$d @@ -590,6 +591,7 @@ lösen Ändern Zum Kalender hinzufügen + Cancel Keine Lektionen Thema wählen @@ -611,6 +613,8 @@ Farbschema der Noten Schulfachen sortieren Sprache + Menu configuration + Set the order of functions in the menu Benachrichtigungen Sonstiges Benachrichtigungen anzeigen @@ -712,6 +716,10 @@ Ein Update wurde gerade heruntergeladen. 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 + Restart Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 667231274..3875b3d98 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -26,6 +26,7 @@ Student info Dashboard Notifications center + Menu configuartion Semester %1$d, %2$d/%3$d @@ -590,6 +591,7 @@ Undo Change Add to calendar + Cancel No lessons Choose theme @@ -611,6 +613,8 @@ Grades color scheme Subjects sorting Language + Menu configuration + Set the order of functions in the menu Notifications Other Show notifications @@ -712,6 +716,10 @@ 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 No internet connection An error occurred. Check your device clock diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4891015d2..7f0c32915 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -26,6 +26,7 @@ Informacje o uczniu Start Centrum powiadomień + Konfiguracja menu Semestr %1$d, %2$d/%3$d @@ -678,6 +679,7 @@ Cofnij Zmień Dodaj do kalendarza + Anuluj Brak lekcji Wybierz motyw @@ -699,6 +701,8 @@ Schemat kolorów ocen Sortowanie przedmiotów Język + Konfiguracja menu + Ustaw kolejność funkcji w menu Powiadomienia Inne Pokazuj powiadomienia @@ -800,6 +804,10 @@ Aktualizacja została pobrana. Uruchom ponownie Aktualizacja nie powiodła się! Wulkanowy może nie działać prawidłowo. Rozważ aktualizację + + Ponowne uruchomienie aplikacji + W celu zapisania zmian aplikacja musi zostać ponownie uruchomiona + Uruchom ponownie Brak połączenia z internetem Wystąpił błąd. Sprawdź poprawność daty w urządzeniu diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 01e43183f..42e1e0bb9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -15,8 +15,8 @@ Отладка уведомлений Разработчики Лицензии - Письма - Написать + Сообщения + Новое сообщение Новое домашнее задание Замечания и свершения Домашние задания @@ -26,6 +26,7 @@ Информация о ученике Главная Центр уведомлений + Настройка меню %1$d семестр, %2$d/%3$d @@ -55,7 +56,7 @@ Неверный symbol Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+ Данный ученик уже авторизован - 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 + Symbol можно найти на странице регистрации в  Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nУбедитесь, что вы выбрали соответствующий тип дневника в поле Тип дневника UONET+ на первом экране входа Выберите учеников для авторизации в приложении Другие варианты В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств @@ -72,14 +73,14 @@ Восстановить Ученик уже авторизован Стандартный - Other search locations - No active students found - Enter a different symbol + Другие места поиска + Не найдено активных учеников + Введите другой symbol - Enable notifications - Enable notifications so you don\'t miss message from teacher or new grade - Skip - Enable + Включить уведомления + Включить уведомления, чтобы вы не пропустили сообщение от учителя или новую оценку + Пропустить + Включить Менеджер аккаунтов Войти @@ -294,7 +295,7 @@ Отправленные Корзина (нет темы) - Нет писем + Нет сообщений От: Кому: Дата: %1$s @@ -304,7 +305,7 @@ Отменить выбор Перенести в корзину Удалить навсегда - Письмо успешно удалено + Сообщение успешно удалено ученик родитель опекун @@ -313,8 +314,8 @@ Печать Тема Содержание - Письмо успешно отправлено - Письма не существует + Сообщение успешно отправлено + Сообщения не существует Вы должны выбрать как минимум одного получателя Текст сообщения должен содержать как минимум 3 знака Все почтовые ящики @@ -334,8 +335,8 @@ Новые сообщения Новые сообщения - Вы хотите восстановить черновик письма? - Вы хотите восстановить черновик письма с получателями: %s? + Вы хотите восстановить черновик сообщения? + Вы хотите восстановить черновик сообщения с получателями: %s? Вы получили %1$d новое сообщение Вы получили %1$d новых сообщения @@ -493,8 +494,8 @@ Присутствует на встрече Повестка дня - Place - Topic + Место + Тема Объявления школы Нет школьных объявлений @@ -678,6 +679,7 @@ Отменить Изменить Добавить в календарь + Отменить Нет уроков Выбрать тему @@ -699,6 +701,8 @@ Цветовая схема оценок Сортировка уроков Язык + Настройка меню + Установить порядок функций в меню Уведомления Прочее Показывать уведомления @@ -780,7 +784,7 @@ Новые встречи Новые тесты Счастливый номер - Новые письма + Новые сообщения Новые замечания Новые школьные объявления Push-уведомления @@ -800,6 +804,10 @@ Только что было скачано обновление. Перезапустить Не удалось обновить! Wulkanowy может работать некорректно. Рассмотрите возможность обновления + + Перезапуск приложение + Для сохранения изменений необходимо перезапустить приложение + Перезапустить Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4189c5349..915e2ef47 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -26,6 +26,7 @@ Informácie o žiakovi Domov Centrum oznámení + Menu configuartion Semester %1$d, %2$d/%3$d @@ -678,6 +679,7 @@ Vrátiť Zmeniť Pridať do kalendára + Cancel Žiadne lekcie Vybrať motív @@ -699,6 +701,8 @@ Známky farebnú schému Triedenie predmetov Jazyk + Menu configuration + Set the order of functions in the menu Oznámenia Iné Zobraziť oznámenia @@ -800,6 +804,10 @@ Aktualizácia bola stiahnutá. Reštartovať Aktualizácia zlyhala! Wulkanowy nemusí fungovať správne. Zvážte aktualizáciu + + Application restart + The application must restart for the changes to be saved + Restart Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 99c340650..0c7369040 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -26,6 +26,7 @@ Інформація про учня Головна Центр сповіщень + Конфігурація меню %1$d семестр, %2$d/%3$d @@ -55,7 +56,7 @@ Неправильний symbol Студента не знайдено. Перевірте symbol та обранний тип щоденника UONET+ Цього учня вже авторизовано - 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 + Symbol можно знайти на сторінці щоденника у Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nПереконайтеся, що ви вказали відповідний щоденник у полі Тип щоденника UONET+ на першому екрані логування Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності та уроків, інформація про школу та список зареєстрованих пристроїв @@ -72,14 +73,14 @@ Відновити Учня вже авторизовано Стандартний - Other search locations - No active students found - Enter a different symbol + Інші розташування пошуку + Активних учнів не знайдено + Введіть інший symbol - Enable notifications - Enable notifications so you don\'t miss message from teacher or new grade - Skip - Enable + Увімкнути сповіщення + Увімкнути сповіщення, щоб не пропустити лист від вчителя або нову оцінку + Пропустити + Увімкнути Змінити облікові записи Увійти @@ -493,8 +494,8 @@ Присутність на зустрічі Порядок денний - Place - Topic + Місце + Тема Оголошення школи Немає шкільних оголошень @@ -522,7 +523,7 @@ Ви впевнені, що хочете вийти з цього облікового запису? Вийти з облікового запису учня Обліковий запис учня - Обліковий запис батька + Обліковий запис родителя Змінити дані Змінити облікові записи Вибрати учня @@ -678,6 +679,7 @@ Відмінити Змінити Додати у календар + Скасувати Немаэ уроків Увібрати тему @@ -699,6 +701,8 @@ Схема кольорів оцінок Сортування предметів Мова + Конфігурація меню + Встановити порядок функцій в меню Повідомлення Інше Показувати повідомлення @@ -800,6 +804,10 @@ Щойно завантажено оновлення. Перезавантажити Помилка оновлення! Wulkanowy може не працювати належним чином. Подумайте про оновлення + + Перезавантаження додатку + Додаток потрібно перезавантажити для збереження змін + Перезавантажити Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою From f11354dd3501953403b47f63b6f67ea45564d9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 14 Jan 2023 15:48:58 +0100 Subject: [PATCH 137/545] Fix marking message as read (#2102) --- app/build.gradle | 2 +- .../github/wulkanowy/data/repositories/MessageRepository.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fff19ebe3..c38fc4073 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.1" + implementation "io.github.wulkanowy:sdk:1.9.2-SNAPSHOT" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 6dfc3ab73..53d9beadd 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 @@ -103,7 +103,10 @@ class MessageRepository @Inject constructor( messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, fetch = { - sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead) + sdk.init(student).getMessageDetails( + messageKey = it!!.message.messageGlobalKey, + markAsRead = message.unread && markAsRead, + ) }, saveFetchResult = { old, new -> checkNotNull(old) { "Fetched message no longer exist!" } From a495fcbc5f30decab849c0c9a7f76000192d91a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 2 Mar 2023 18:01:48 +0100 Subject: [PATCH 138/545] Fix saving attachements with same url but from different messages (#2137) --- .../55.json | 2435 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../data/db/entities/MessageAttachment.kt | 10 +- .../data/db/migrations/Migration55.kt | 17 + .../wulkanowy/data/mappers/MessageMapper.kt | 1 - 5 files changed, 2458 insertions(+), 8 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json new file mode 100644 index 000000000..60c2efbe0 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/55.json @@ -0,0 +1,2435 @@ +{ + "formatVersion": 1, + "database": { + "version": 55, + "identityHash": "cba22eea6d26cf4d6b9a388ba3329a12", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "message_global_key", + "url", + "filename" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "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": { + "columnNames": [ + "globalKey" + ], + "autoGenerate": false + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, `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": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cba22eea6d26cf4d6b9a388ba3329a12')" + ] + } +} \ 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 cfb53485f..0aea86dab 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 @@ -48,6 +48,7 @@ import javax.inject.Singleton AutoMigration(from = 46, to = 47), AutoMigration(from = 47, to = 48), AutoMigration(from = 51, to = 52), + AutoMigration(from = 54, to = 55, spec = Migration55::class), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -56,7 +57,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 54 + const val VERSION_SCHEMA = 55 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt index 93f042999..6f0c84ad7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageAttachment.kt @@ -2,16 +2,14 @@ package io.github.wulkanowy.data.db.entities import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.PrimaryKey import java.io.Serializable -@Entity(tableName = "MessageAttachments") +@Entity( + tableName = "MessageAttachments", + primaryKeys = ["message_global_key", "url", "filename"], +) data class MessageAttachment( - @PrimaryKey - @ColumnInfo(name = "real_id") - val realId: Int, - @ColumnInfo(name = "message_global_key") val messageGlobalKey: String, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt new file mode 100644 index 000000000..424be171b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration55.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.DeleteColumn +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase + +@DeleteColumn( + tableName = "MessageAttachments", + columnName = "real_id", +) +class Migration55 : AutoMigrationSpec { + + override fun onPostMigrate(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Messages") + db.execSQL("DELETE FROM MessageAttachments") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 2ede5aa1b..6fc5dc950 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -40,7 +40,6 @@ fun List.mapToEntities( fun List.mapToEntities(messageGlobalKey: String) = map { MessageAttachment( messageGlobalKey = messageGlobalKey, - realId = it.url.hashCode(), url = it.url, filename = it.filename ) From fba86930feb8dea805950d2e094426b5a7c37f11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 21:46:18 +0000 Subject: [PATCH 139/545] Bump com.android.tools.build:gradle from 7.4.1 to 7.4.2 (#2140) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index eed6655ad..c14e0dbd3 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.1' + classpath 'com.android.tools.build:gradle:7.4.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.8.1.300' From 9ce1ba75b2ffb50baf4d1a6f44b295d998c2c690 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 21:46:37 +0000 Subject: [PATCH 140/545] Bump com.google.firebase:firebase-bom from 31.2.2 to 31.2.3 (#2141) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a470dae88..d583891ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.2.2') + playImplementation platform('com.google.firebase:firebase-bom:31.2.3') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 5331bf90cde50e847aa6e7f56a87a569691d8a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 7 Mar 2023 18:10:20 +0100 Subject: [PATCH 141/545] Use user agent template from firebase remote config (#2139) * Use user agent template from firebase remote config * Improve base class usage, activation refactor --- app/build.gradle | 1 + .../wulkanowy/utils/RemoteConfigHelper.kt | 7 ++++ .../wulkanowy/utils/RemoteConfigHelper.kt | 7 ++++ .../java/io/github/wulkanowy/WulkanowyApp.kt | 4 +++ .../io/github/wulkanowy/data/DataModule.kt | 4 ++- .../wulkanowy/utils/BaseRemoteConfigHelper.kt | 9 +++++ .../wulkanowy/utils/RemoteConfigDefaults.kt | 8 +++++ .../wulkanowy/utils/RemoteConfigHelper.kt | 35 +++++++++++++++++++ 8 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt create mode 100644 app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt diff --git a/app/build.gradle b/app/build.gradle index c38fc4073..470b4e29f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,6 +246,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' + 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:21.4.0' diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt new file mode 100644 index 000000000..88f2598f8 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper() diff --git a/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt new file mode 100644 index 000000000..88f2598f8 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RemoteConfigHelper @Inject constructor() : BaseRemoteConfigHelper() diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 7d2eeb1ec..a39a38745 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -34,11 +34,15 @@ class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var adsHelper: AdsHelper + @Inject + lateinit var remoteConfigHelper: RemoteConfigHelper + override fun onCreate() { super.onCreate() initializeAppLanguage() themeManager.applyDefaultTheme() adsHelper.initialize() + remoteConfigHelper.initialize() initLogging() } diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 22123cbec..e538b2b2f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -19,6 +19,7 @@ import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.RemoteConfigHelper import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType @@ -36,10 +37,11 @@ internal class DataModule { @Singleton @Provides - fun provideSdk(chuckerInterceptor: ChuckerInterceptor) = + fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) = Sdk().apply { androidVersion = android.os.Build.VERSION.RELEASE buildTag = android.os.Build.MODEL + userAgentTemplate = remoteConfig.userAgentTemplate setSimpleHttpLogger { Timber.d(it) } // for debug only diff --git a/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt new file mode 100644 index 000000000..002612a87 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/BaseRemoteConfigHelper.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.utils + +abstract class BaseRemoteConfigHelper { + + open fun initialize() = Unit + + open val userAgentTemplate: String + get() = RemoteConfigDefaults.USER_AGENT_TEMPLATE.value as String +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt new file mode 100644 index 000000000..6e8f7ae65 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/RemoteConfigDefaults.kt @@ -0,0 +1,8 @@ +package io.github.wulkanowy.utils + +enum class RemoteConfigDefaults(val value: Any) { + USER_AGENT_TEMPLATE(""), + ; + + val key get() = name.lowercase() +} diff --git a/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt new file mode 100644 index 000000000..379932f3a --- /dev/null +++ b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import com.google.firebase.FirebaseApp +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RemoteConfigHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val appInfo: AppInfo, +) : BaseRemoteConfigHelper() { + + fun initialize() { + FirebaseApp.initializeApp(context) + + Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings { + fetchTimeoutInSeconds = 3 + if (appInfo.isDebug) { + minimumFetchIntervalInSeconds = 0 + } + }) + Firebase.remoteConfig.setDefaultsAsync(RemoteConfigDefaults.values().associate { + it.key to it.value + }) + Firebase.remoteConfig.fetchAndActivate() + } + + override val userAgentTemplate: String + get() = Firebase.remoteConfig.getString(RemoteConfigDefaults.USER_AGENT_TEMPLATE.key) +} From ef398f7409c9bbdde3d0dbed115c2a38ec67eecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 7 Mar 2023 22:29:37 +0100 Subject: [PATCH 142/545] Add missing override to RemoteConfigHelper.initialize() --- .../play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt index 379932f3a..d17bfb4eb 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/RemoteConfigHelper.kt @@ -15,7 +15,7 @@ class RemoteConfigHelper @Inject constructor( private val appInfo: AppInfo, ) : BaseRemoteConfigHelper() { - fun initialize() { + override fun initialize() { FirebaseApp.initializeApp(context) Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings { From ee5ac46493a04e5d49f411d611e75b8acb676588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 8 Mar 2023 09:11:25 +0100 Subject: [PATCH 143/545] Show invalid symbol message when nonexistent symbol entered (#2143) --- .../LoginStudentSelectPresenter.kt | 9 ++++-- .../login/symbol/LoginSymbolFragment.kt | 7 ++++ .../login/symbol/LoginSymbolPresenter.kt | 32 +++++++++++++------ .../modules/login/symbol/LoginSymbolView.kt | 2 ++ 4 files changed, 37 insertions(+), 13 deletions(-) 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 9148b6ab9..7e1fe3b21 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 @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData @@ -158,7 +159,7 @@ class LoginStudentSelectPresenter @Inject constructor( isNotEmptySymbolsExist: Boolean, ) = buildList { val filteredEmptySymbols = emptySymbols.filter { - it.error !is AccountPermissionException + it.error !is InvalidSymbolException }.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() } if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) { @@ -281,7 +282,7 @@ class LoginStudentSelectPresenter @Inject constructor( private fun onEmailClick() { view?.openEmail(lastError?.message.ifNullOrBlank { loginData.baseUrl + "/" + loginData.symbol + "\n" + registerUser.symbols.filterNot { - it.error is AccountPermissionException && it.symbol != loginData.symbol + (it.error is AccountPermissionException || it.error is InvalidSymbolException) && it.symbol != loginData.symbol }.joinToString(";\n") { symbol -> buildString { append(" -") @@ -297,7 +298,9 @@ class LoginStudentSelectPresenter @Inject constructor( } }) } - } + } + "\nPozostałe: " + registerUser.symbols.filter { + it.error is AccountPermissionException || it.error is InvalidSymbolException + }.joinToString(", ") { it.symbol } }) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 67416cb63..692aaeb76 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -93,6 +93,13 @@ class LoginSymbolFragment : } } + override fun setErrorSymbolInvalid() { + with(binding.loginSymbolNameLayout) { + requestFocus() + error = getString(R.string.login_invalid_symbol) + } + } + override fun setErrorSymbolRequire() { setErrorSymbol(getString(R.string.error_field_required)) } 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 a6ccd7a57..03ea95fa6 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 @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -61,11 +62,11 @@ class LoginSymbolPresenter @Inject constructor( email = loginData.login, password = loginData.password, scrapperBaseUrl = loginData.baseUrl, - symbol = view?.symbolValue.orEmpty(), + symbol = loginData.symbol.orEmpty(), ) - }.onEach { - registerUser = it.dataOrNull - when (it) { + }.onEach { user -> + registerUser = user.dataOrNull + when (user) { is Resource.Loading -> view?.run { Timber.i("Login with symbol started") hideSoftKeyboard() @@ -73,7 +74,7 @@ class LoginSymbolPresenter @Inject constructor( showContent(false) } is Resource.Success -> { - when (it.data.symbols.size) { + when (user.data.symbols.size) { 0 -> { Timber.i("Login with symbol result: Empty student list") view?.run { @@ -82,8 +83,19 @@ class LoginSymbolPresenter @Inject constructor( } } else -> { - Timber.i("Login with symbol result: Success") - view?.navigateToStudentSelect(loginData, requireNotNull(it.data)) + val enteredSymbolDetails = user.data.symbols + .firstOrNull() + ?.takeIf { it.symbol == loginData.symbol } + + if (enteredSymbolDetails?.error is InvalidSymbolException) { + view?.run { + setErrorSymbolInvalid() + showContact(true) + } + } else { + Timber.i("Login with symbol result: Success") + view?.navigateToStudentSelect(loginData, requireNotNull(user.data)) + } } } analytics.logEvent( @@ -102,10 +114,10 @@ class LoginSymbolPresenter @Inject constructor( "students" to -1, "scrapperBaseUrl" to loginData.baseUrl, "symbol" to view?.symbolValue, - "error" to it.error.message.ifNullOrBlank { "No message" } + "error" to user.error.message.ifNullOrBlank { "No message" } ) - loginErrorHandler.dispatch(it.error) - lastError = it.error + loginErrorHandler.dispatch(user.error) + lastError = user.error view?.showContact(true) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 6b62d1f7f..6585c00f4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -16,6 +16,8 @@ interface LoginSymbolView : BaseView { fun setErrorSymbolIncorrect() + fun setErrorSymbolInvalid() + fun setErrorSymbolRequire() fun setErrorSymbol(message: String) From 1b40e339b7e321cc5840db169a232ec9aa6c6761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 8 Mar 2023 21:28:48 +0100 Subject: [PATCH 144/545] Version 1.9.2 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 470b4e29f..255e0098a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 120 - versionName "1.9.1" + versionCode 121 + versionName "1.9.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.2-SNAPSHOT" + implementation "io.github.wulkanowy:sdk:1.9.2" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' 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 76c6a0cb2..b3fec438f 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,10 +1,7 @@ -Wersja 1.9.1 +Wersja 1.9.2 -- dodaliśmy obsługę Androida 13 (w tym ikona aplikacji obsługująca Material You) -- przerobiliśmy ekran wyboru ucznia przy pierwszym logowaniu -- naprawiliśmy usuwanie wiadomości w niektórych przypadkach -- naprawiliśmy błąd występujący przy resecie hasła -- naprawiliśmy literówkę w tytule domyślnej treści wiadomości usprawiedliwiania -- naprawiliśmy nazwę aplikacji przy ustawionym w telefonie jezyku francuskim +- naprawiliśmy oznaczanie wiadomości jako odczytanych (problem dotyczył głównie kont rodziców z wieloma dziećmi w tej samej szkole) +- naprawiliśmy zapisywanie załączników do wiadomości w sytuacji, gdy ten sam załącznik był dodany do więcej niż jednej wiadomości +- usprawniliśmy ekran z wyborem uczniów i wpisywaniem symbolu przy pierwszym logowaniu Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From cc2079f4c93030d440b2e12cfeb6dc5a3b879dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 8 Mar 2023 21:36:57 +0100 Subject: [PATCH 145/545] New Crowdin updates (#2138) --- app/src/main/res/values-cs/strings.xml | 14 +++++++------- app/src/main/res/values-sk/strings.xml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 47f7707f5..1897a48b4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -26,7 +26,7 @@ Informace o žáku Domů Centrum oznámení - Menu configuartion + Konfigurace menu Semestr %1$d, %2$d/%3$d @@ -679,7 +679,7 @@ Vrátit Změnit Přidat do kalendáře - Cancel + Zrušit Žádné lekce Vybrat motiv @@ -701,8 +701,8 @@ Známky barevné schéma Třídění předmětů Jazyk - Menu configuration - Set the order of functions in the menu + Konfigurace menu + Nastavit pořadí funkcí v menu Oznámení Jiné Zobrazit oznámení @@ -805,9 +805,9 @@ Restartovat Aktualizace selhala! Wulkanowy nemusí fungovat správně. Zvažte aktualizaci - Application restart - The application must restart for the changes to be saved - Restart + Restartování aplikace + Pro uložení změn je nutné aplikaci restartovat + Restartovat Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 915e2ef47..8979a95fc 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -26,7 +26,7 @@ Informácie o žiakovi Domov Centrum oznámení - Menu configuartion + Konfigurácia menu Semester %1$d, %2$d/%3$d @@ -679,7 +679,7 @@ Vrátiť Zmeniť Pridať do kalendára - Cancel + Zrušiť Žiadne lekcie Vybrať motív @@ -701,8 +701,8 @@ Známky farebnú schému Triedenie predmetov Jazyk - Menu configuration - Set the order of functions in the menu + Konfigurácia menu + Nastaviť poradie funkcií v menu Oznámenia Iné Zobraziť oznámenia @@ -805,9 +805,9 @@ Reštartovať Aktualizácia zlyhala! Wulkanowy nemusí fungovať správne. Zvážte aktualizáciu - Application restart - The application must restart for the changes to be saved - Restart + Reštartovanie aplikácie + Pre uloženie zmien je nutné aplikáciu reštartovať + Reštartovať Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia From a350a167f3611d1b3f77b6d199a61db4f21d80a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 18:14:31 +0000 Subject: [PATCH 146/545] Bump androidx.lifecycle:lifecycle-livedata-ktx from 2.5.1 to 2.6.0 (#2146) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1e4de10ef..9ca1c9d4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,7 +214,7 @@ dependencies { implementation "androidx.work:work-runtime-ktx:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0" implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" From 060bab46e210f554759d7e3340598b225ff57739 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 18:14:50 +0000 Subject: [PATCH 147/545] Bump androidx.recyclerview:recyclerview from 1.2.1 to 1.3.0 (#2145) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 9ca1c9d4b..1eddaecca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -201,7 +201,7 @@ dependencies { implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" - implementation "androidx.recyclerview:recyclerview:1.2.1" + implementation "androidx.recyclerview:recyclerview:1.3.0" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" From a2a31df98ee4b425c90a7c9a75a7323b28361192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 18 Mar 2023 00:41:45 +0100 Subject: [PATCH 148/545] Fix crash on deserialize parcelable array (#2149) --- .../main/java/io/github/wulkanowy/utils/BundleExtension.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt index d0d47025e..d3c9f8006 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.content.Intent import android.os.Build import android.os.Bundle +import android.os.Parcelable import java.io.Serializable inline fun Bundle.serializable(key: String): T = when { @@ -15,10 +16,10 @@ inline fun Bundle.nullableSerializable(key: String): else -> @Suppress("DEPRECATION") getSerializable(key) as T? } -@Suppress("DEPRECATION", "UNCHECKED_CAST") -inline fun Bundle.parcelableArray(key: String): Array? = when { +@Suppress("UNCHECKED_CAST") +inline fun Bundle.parcelableArray(key: String): Array? = when { Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java) - else -> getParcelableArray(key) as Array? + else -> @Suppress("DEPRECATION") getParcelableArray(key) as Array? } inline fun Intent.serializable(key: String): T = when { From b3c6e2004bd59d80c013236304612c083fe5f6cc Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Sat, 18 Mar 2023 15:10:12 +0100 Subject: [PATCH 149/545] Make GradeAverageProvider reactive to configuration changes (#1698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../repositories/PreferencesRepository.kt | 68 +- .../ui/modules/grade/GradeAverageProvider.kt | 148 ++-- .../modules/grade/GradeAverageProviderTest.kt | 691 +++++++++++++----- 3 files changed, 632 insertions(+), 275 deletions(-) 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 29a65a96d..f6da6a63e 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 @@ -2,9 +2,11 @@ package io.github.wulkanowy.data.repositories import android.content.Context import android.content.SharedPreferences +import androidx.annotation.StringRes import androidx.core.content.edit import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.Preference +import com.fredporciuncula.flow.preferences.Serializer import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.* @@ -35,20 +37,29 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_attendance_present ) - val gradeAverageMode: GradeAverageMode - get() = GradeAverageMode.getByValue( - getString( - R.string.pref_key_grade_average_mode, - R.string.pref_default_grade_average_mode - ) + private val gradeAverageModePref: Preference + get() = getObjectFlow( + R.string.pref_key_grade_average_mode, + R.string.pref_default_grade_average_mode, + object : Serializer { + override fun serialize(value: GradeAverageMode) = value.value + override fun deserialize(serialized: String) = + GradeAverageMode.getByValue(serialized) + }, ) - val gradeAverageForceCalc: Boolean - get() = getBoolean( - R.string.pref_key_grade_average_force_calc, - R.bool.pref_default_grade_average_force_calc + val gradeAverageModeFlow: Flow + get() = gradeAverageModePref.asFlow() + + private val gradeAverageForceCalcPref: Preference + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_grade_average_force_calc), + context.resources.getBoolean(R.bool.pref_default_grade_average_force_calc) ) + val gradeAverageForceCalcFlow: Flow + get() = gradeAverageForceCalcPref.asFlow() + val gradeExpandMode: GradeExpandMode get() = GradeExpandMode.getByValue( getString( @@ -138,12 +149,24 @@ class PreferencesRepository @Inject constructor( R.string.pref_default_grade_modifier_plus ).toDouble() + val gradePlusModifierFlow: Flow + get() = getStringFlow( + R.string.pref_key_grade_modifier_plus, + R.string.pref_default_grade_modifier_plus + ).asFlow().map { it.toDouble() } + val gradeMinusModifier: Double get() = getString( R.string.pref_key_grade_modifier_minus, R.string.pref_default_grade_modifier_minus ).toDouble() + val gradeMinusModifierFlow: Flow + get() = getStringFlow( + R.string.pref_key_grade_modifier_minus, + R.string.pref_default_grade_modifier_minus + ).asFlow().map { it.toDouble() } + val fillMessageContent: Boolean get() = getBoolean( R.string.pref_key_fill_message_content, @@ -191,11 +214,11 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_subjects_without_grades ) - val isOptionalArithmeticAverage: Boolean - get() = getBoolean( - R.string.pref_key_optional_arithmetic_average, - R.bool.pref_default_optional_arithmetic_average - ) + val isOptionalArithmeticAverageFlow: Flow + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_optional_arithmetic_average), + context.resources.getBoolean(R.bool.pref_default_optional_arithmetic_average) + ).asFlow() var lasSyncDate: Instant? get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) @@ -342,6 +365,21 @@ class PreferencesRepository @Inject constructor( private fun getLong(id: String, default: Int) = sharedPref.getLong(id, context.resources.getString(default).toLong()) + private fun getStringFlow(id: Int, default: Int) = + flowSharedPref.getString(context.getString(id), context.getString(default)) + + private fun getObjectFlow( + @StringRes id: Int, + @StringRes default: Int, + serializer: Serializer + ): Preference = flowSharedPref.getObject( + key = context.getString(id), + serializer = serializer, + defaultValue = serializer.deserialize( + flowSharedPref.getString(context.getString(default)).get() + ) + ) + private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: String, default: Int) = diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index b6733d4f2..38bae3761 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -12,70 +12,91 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier -import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import javax.inject.Inject -@OptIn(FlowPreview::class) +@OptIn(ExperimentalCoroutinesApi::class) class GradeAverageProvider @Inject constructor( private val semesterRepository: SemesterRepository, private val gradeRepository: GradeRepository, private val preferencesRepository: PreferencesRepository ) { - private val plusModifier get() = preferencesRepository.gradePlusModifier + private data class AverageCalcParams( + val gradeAverageMode: GradeAverageMode, + val forceAverageCalc: Boolean, + val isOptionalArithmeticAverage: Boolean, + val plusModifier: Double, + val minusModifier: Double, + ) - private val minusModifier get() = preferencesRepository.gradeMinusModifier - - private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage - - fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = + fun getGradesDetailsWithAverage( + student: Student, + semesterId: Int, + forceRefresh: Boolean + ): Flow>> = combine( + flow = preferencesRepository.gradeAverageModeFlow, + flow2 = preferencesRepository.gradeAverageForceCalcFlow, + flow3 = preferencesRepository.isOptionalArithmeticAverageFlow, + flow4 = preferencesRepository.gradePlusModifierFlow, + flow5 = preferencesRepository.gradeMinusModifierFlow, + ) { gradeAverageMode, forceAverageCalc, isOptionalArithmeticAverage, plusModifier, minusModifier -> + AverageCalcParams( + gradeAverageMode = gradeAverageMode, + forceAverageCalc = forceAverageCalc, + isOptionalArithmeticAverage = isOptionalArithmeticAverage, + plusModifier = plusModifier, + minusModifier = minusModifier, + ) + }.flatMapLatest { params -> flatResourceFlow { val semesters = semesterRepository.getSemesters(student) - - when (preferencesRepository.gradeAverageMode) { + when (params.gradeAverageMode) { ONE_SEMESTER -> getGradeSubjects( student = student, semester = semesters.single { it.semesterId == semesterId }, - forceRefresh = forceRefresh + forceRefresh = forceRefresh, + params = params, ) BOTH_SEMESTERS -> calculateCombinedAverage( student = student, semesters = semesters, semesterId = semesterId, forceRefresh = forceRefresh, - averageMode = BOTH_SEMESTERS + config = params, ) ALL_YEAR -> calculateCombinedAverage( student = student, semesters = semesters, semesterId = semesterId, forceRefresh = forceRefresh, - averageMode = ALL_YEAR + config = params, ) } - }.distinctUntilChanged() + } + } private fun calculateCombinedAverage( student: Student, semesters: List, semesterId: Int, forceRefresh: Boolean, - averageMode: GradeAverageMode + config: AverageCalcParams, ): Flow>> { - val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc val selectedSemester = semesters.single { it.semesterId == semesterId } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } val selectedSemesterGradeSubjects = - getGradeSubjects(student, selectedSemester, forceRefresh) + getGradeSubjects(student, selectedSemester, forceRefresh, config) if (selectedSemester == firstSemester) return selectedSemesterGradeSubjects - val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh) + val firstSemesterGradeSubjects = + getGradeSubjects(student, firstSemester, forceRefresh, config) return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> if (firstSemesterGradeSubject.errorOrNull != null) { @@ -91,21 +112,21 @@ class GradeAverageProvider @Inject constructor( val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() .singleOrNull { it.subject == secondSemesterSubject.subject } - val updatedAverage = if (averageMode == ALL_YEAR) { + val updatedAverage = if (config.gradeAverageMode == ALL_YEAR) { calculateAllYearAverage( student = student, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, - isGradeAverageForceCalc = isGradeAverageForceCalc, secondSemesterSubject = secondSemesterSubject, - firstSemesterSubject = firstSemesterSubject + firstSemesterSubject = firstSemesterSubject, + config = config, ) } else { calculateBothSemestersAverage( student = student, isAnyVulcanAverage = isAnyVulcanAverageInFirstSemester || isAnyVulcanAverageInSecondSemester, - isGradeAverageForceCalc = isGradeAverageForceCalc, secondSemesterSubject = secondSemesterSubject, - firstSemesterSubject = firstSemesterSubject + firstSemesterSubject = firstSemesterSubject, + config = config ) } secondSemesterSubject.copy(average = updatedAverage) @@ -117,17 +138,17 @@ class GradeAverageProvider @Inject constructor( private fun calculateAllYearAverage( student: Student, isAnyVulcanAverage: Boolean, - isGradeAverageForceCalc: Boolean, secondSemesterSubject: GradeSubject, - firstSemesterSubject: GradeSubject? - ) = if (!isAnyVulcanAverage || isGradeAverageForceCalc) { - val updatedSecondSemesterGrades = - secondSemesterSubject.grades.updateModifiers(student) - val updatedFirstSemesterGrades = - firstSemesterSubject?.grades?.updateModifiers(student).orEmpty() + firstSemesterSubject: GradeSubject?, + config: AverageCalcParams, + ) = if (!isAnyVulcanAverage || config.forceAverageCalc) { + val updatedSecondSemesterGrades = secondSemesterSubject.grades + .updateModifiers(student, config) + val updatedFirstSemesterGrades = firstSemesterSubject?.grades + ?.updateModifiers(student, config).orEmpty() (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage( - isOptionalArithmeticAverage + config.isOptionalArithmeticAverage ) } else { secondSemesterSubject.average @@ -136,32 +157,35 @@ class GradeAverageProvider @Inject constructor( private fun calculateBothSemestersAverage( student: Student, isAnyVulcanAverage: Boolean, - isGradeAverageForceCalc: Boolean, secondSemesterSubject: GradeSubject, - firstSemesterSubject: GradeSubject? - ): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) { - val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 + firstSemesterSubject: GradeSubject?, + config: AverageCalcParams, + ): Double { + return if (!isAnyVulcanAverage || config.forceAverageCalc) { + val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 + val secondSemesterAverage = secondSemesterSubject.grades + .updateModifiers(student, config) + .calcAverage(config.isOptionalArithmeticAverage) + val firstSemesterAverage = firstSemesterSubject?.grades + ?.updateModifiers(student, config) + ?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage - val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student) - .calcAverage(isOptionalArithmeticAverage) - val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student) - ?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage + (secondSemesterAverage + firstSemesterAverage) / divider + } else { + val divider = if (secondSemesterSubject.average > 0) 2 else 1 - (secondSemesterAverage + firstSemesterAverage) / divider - } else { - val divider = if (secondSemesterSubject.average > 0) 2 else 1 - - (secondSemesterSubject.average + (firstSemesterSubject?.average - ?: secondSemesterSubject.average)) / divider + secondSemesterSubject.average.plus( + (firstSemesterSubject?.average ?: secondSemesterSubject.average) + ) / divider + } } private fun getGradeSubjects( student: Student, semester: Semester, - forceRefresh: Boolean + forceRefresh: Boolean, + params: AverageCalcParams, ): Flow>> { - val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc - return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) .mapResourceData { res -> val (details, summaries) = res @@ -172,13 +196,15 @@ class GradeAverageProvider @Inject constructor( student = student, semester = semester, grades = allGrades.toList(), - calcAverage = isAnyAverage + calcAverage = isAnyAverage, + params = params, ).map { summary -> val grades = allGrades[summary.subject].orEmpty() GradeSubject( subject = summary.subject, - average = if (!isAnyAverage || isGradeAverageForceCalc) { - grades.updateModifiers(student).calcAverage(isOptionalArithmeticAverage) + average = if (!isAnyAverage || params.forceAverageCalc) { + grades.updateModifiers(student, params) + .calcAverage(params.isOptionalArithmeticAverage) } else summary.average, points = summary.pointsSum, summary = summary, @@ -195,7 +221,8 @@ class GradeAverageProvider @Inject constructor( student: Student, semester: Semester, grades: List>>, - calcAverage: Boolean + calcAverage: Boolean, + params: AverageCalcParams, ): List { if (isNotEmpty() && size > grades.size) return this @@ -211,15 +238,16 @@ class GradeAverageProvider @Inject constructor( proposedPoints = "", finalPoints = "", pointsSum = "", - average = if (calcAverage) details.updateModifiers(student) - .calcAverage(isOptionalArithmeticAverage) else .0 + average = if (calcAverage) details.updateModifiers(student, params) + .calcAverage(params.isOptionalArithmeticAverage) else .0 ) } } - private fun List.updateModifiers(student: Student): List { - return if (student.loginMode == Sdk.Mode.SCRAPPER.name) { - map { it.changeModifier(plusModifier, minusModifier) } - } else this - } + private fun List.updateModifiers( + student: Student, + params: AverageCalcParams, + ): List = if (student.loginMode == Sdk.Mode.SCRAPPER.name) { + map { it.changeModifier(params.plusModifier, params.minusModifier) } + } else this } 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 a6ecdc26b..10c84efcf 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 @@ -1,14 +1,12 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.Status @@ -20,6 +18,7 @@ import io.mockk.impl.annotations.MockK import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -31,7 +30,9 @@ import java.time.LocalDate.of class GradeAverageProviderTest { - private suspend fun Flow>.getResult() = toList()[1].dataOrNull!! + private suspend fun Flow>.getResult() = toFirstResult().let { + it.dataOrNull ?: throw it.errorOrNull ?: error("Unknown state") + } @MockK lateinit var preferencesRepository: PreferencesRepository @@ -133,19 +134,23 @@ class GradeAverageProviderTest { fun setUp() { MockKAnnotations.init(this) - every { preferencesRepository.gradeAverageForceCalc } returns false + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) coEvery { semesterRepository.getSemesters(student) } returns semesters - every { preferencesRepository.gradeMinusModifier } returns .33 - every { preferencesRepository.gradePlusModifier } returns .33 + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.33) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) - gradeAverageProvider = GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) + gradeAverageProvider = + GradeAverageProvider(semesterRepository, gradeRepository, preferencesRepository) } @Test fun `calc current semester standard average with no weights`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { gradeRepository.getGrades( student, @@ -154,16 +159,26 @@ class GradeAverageProviderTest { ) } returns resourceFlow { noWeightGrades to noWeightGradesSummary } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(0.0, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 0,0 + assertEquals( + 0.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // from summary: 0,0 } @Test fun `calc current semester arithmetic average with no weights`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns true - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(true) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { gradeRepository.getGrades( student, @@ -172,16 +187,26 @@ class GradeAverageProviderTest { ) } returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(4.0, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 4,0 + assertEquals( + 4.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // from summary: 4,0 } @Test fun `calc current semester average with load from cache sequence`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { emit(Resource.Loading()) @@ -189,7 +214,13 @@ class GradeAverageProviderTest { emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).toList() + } with(items[0]) { assertEquals(Status.LOADING, status) @@ -204,14 +235,18 @@ class GradeAverageProviderTest { assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus + assertEquals( + 3.5, + items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, + .0 + ) // from details and after set custom plus/minus } @Test - fun `calc all year semester average with delayed emit`(){ - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + fun `calc all year semester average with delayed emit`() { + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow { @@ -224,7 +259,13 @@ class GradeAverageProviderTest { emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).toList() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + false + ).toList() + } with(items[0]) { assertEquals(Status.LOADING, status) @@ -235,14 +276,18 @@ class GradeAverageProviderTest { assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus + assertEquals( + 3.5, + items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, + .0 + ) // from details and after set custom plus/minus } @Test fun `calc both semesters average with grade without grade in second semester`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -251,7 +296,13 @@ class GradeAverageProviderTest { false ) } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } - coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + false + ) + } returns resourceFlow { listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf( getSummary(semesters[2].semesterId, "Język polski", 2.5) ) @@ -270,9 +321,9 @@ class GradeAverageProviderTest { @Test fun `calc both semesters average with no grade in second semester but with average in first semester`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -287,7 +338,15 @@ class GradeAverageProviderTest { semesters[2], false ) - } returns resourceFlow { emptyList() to listOf(getSummary(24, "Język polski", .0)) } + } returns resourceFlow { + emptyList() to listOf( + getSummary( + 24, + "Język polski", + .0 + ) + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -302,9 +361,9 @@ class GradeAverageProviderTest { @Test fun `force calc average on no grades`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -334,9 +393,9 @@ class GradeAverageProviderTest { @Test fun `force calc current semester average with default modifiers in scraper mode`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades( @@ -346,21 +405,31 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus + assertEquals( + 3.5, + items.single { it.subject == "Język polski" }.average, + .0 + ) // from details and after set custom plus/minus } @Test fun `force calc current semester average with custom modifiers in scraper mode`() { val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeMinusModifier } returns .33 - every { preferencesRepository.gradePlusModifier } returns .33 + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.33) - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades( @@ -370,21 +439,31 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus + assertEquals( + 3.5, + items.single { it.subject == "Język polski" }.average, + .0 + ) // from details and after set custom plus/minus } @Test fun `force calc current semester average with custom modifiers in api mode`() { val student = student.copy(loginMode = Sdk.Mode.API.name) - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeMinusModifier } returns .33 // useless in this mode - every { preferencesRepository.gradePlusModifier } returns .33 + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) // useless in this mode + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.33) - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades( @@ -394,21 +473,31 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 + assertEquals( + 3.375, + items.single { it.subject == "Język polski" }.average, + .0 + ) // (from details): 3.375 } @Test fun `force calc current semester average with custom modifiers in hybrid mode`() { val student = student.copy(loginMode = Sdk.Mode.HYBRID.name) - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeMinusModifier } returns .33 // useless in this mode - every { preferencesRepository.gradePlusModifier } returns .33 + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) // useless in this mode + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.33) - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades( @@ -418,16 +507,26 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375 + assertEquals( + 3.375, + items.single { it.subject == "Język polski" }.average, + .0 + ) // (from details): 3.375 } @Test fun `calc current semester average`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { gradeRepository.getGrades( student, @@ -436,18 +535,28 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9 + assertEquals( + 2.9, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // from summary: 2,9 assertEquals(3.4, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,4 } @Test fun `force calc current semester average`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ONE_SEMESTER) coEvery { gradeRepository.getGrades( student, @@ -456,18 +565,28 @@ class GradeAverageProviderTest { ) } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5 + assertEquals( + 2.5, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // from details: 2,5 assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0 } @Test fun `force calc full year average when current is first`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades( student, @@ -476,18 +595,32 @@ class GradeAverageProviderTest { ) } returns resourceFlow { firstGrades to firstSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[1].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5 - assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5 + assertEquals( + 3.5, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summary): 3,5 + assertEquals( + 3.5, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from summary): 3,5 } @Test fun `calc full year average when current is first with load from cache sequence`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { emit(Resource.Loading()) @@ -495,7 +628,13 @@ class GradeAverageProviderTest { emit(Resource.Success(firstGrades to firstSummaries)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).toList() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[1].semesterId, + true + ).toList() + } with(items[0]) { assertEquals(Status.LOADING, status) @@ -511,40 +650,74 @@ class GradeAverageProviderTest { } assertEquals(2, items[2].dataOrNull?.size) - assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summary): 3,5 - assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summary): 3,5 + assertEquals( + 3.5, + items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, + .0 + ) // (from summary): 3,5 + assertEquals( + 3.5, + items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, + .0 + ) // (from summary): 3,5 } @Test fun `calc both semesters average`() { - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 3.0), getSummary(22, "Fizyka", 3.5) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 3.5), getSummary(22, "Fizyka", 4.0) ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 - assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 + assertEquals( + 3.25, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries ↑): 3,0 + 3,5 → 3,25 + assertEquals( + 3.75, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from summaries ↑): 3,5 + 4,0 → 3,75 } @Test fun `calc both semesters average when current is second with load from cache sequence`() { - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageForceCalc } returns false + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { emit(Resource.Loading()) emit( @@ -584,7 +757,13 @@ class GradeAverageProviderTest { ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).toList() + } with(items[0]) { assertEquals(Status.LOADING, status) @@ -600,15 +779,23 @@ class GradeAverageProviderTest { } assertEquals(2, items[2].dataOrNull?.size) - assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 - assertEquals(3.75, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 + assertEquals( + 3.25, + items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, + .0 + ) // (from summaries ↑): 3,0 + 3,5 → 3,25 + assertEquals( + 3.75, + items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, + .0 + ) // (from summaries ↑): 3,5 + 4,0 → 3,75 } @Test fun `force calc full year average`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades( student, @@ -616,7 +803,13 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { firstGrades to firstSummaries } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 1.1), getSummary(22, "Fizyka", 7.26) @@ -646,9 +839,9 @@ class GradeAverageProviderTest { @Test fun `calc all year average`() { - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) coEvery { gradeRepository.getGrades( student, @@ -689,9 +882,9 @@ class GradeAverageProviderTest { @Test fun `force calc full year average when current is second with load from cache sequence`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { emit(Resource.Loading()) @@ -718,7 +911,13 @@ class GradeAverageProviderTest { ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).toList() + } with(items[0]) { assertEquals(Status.LOADING, status) @@ -734,15 +933,23 @@ class GradeAverageProviderTest { } assertEquals(2, items[2].dataOrNull?.size) - assertEquals(3.0, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.0, + items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, + .0 + ) // (from details): 3,5 + 2,5 → 3,0 + assertEquals( + 3.25, + items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test fun `force calc both semesters average when no summaries`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -773,14 +980,18 @@ class GradeAverageProviderTest { items.single { it.subject == "Matematyka" }.average, .0 ) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test fun `force calc full year average when no summaries`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades( @@ -820,33 +1031,59 @@ class GradeAverageProviderTest { @Test fun `calc both semesters average when missing summaries in both semesters`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 4.0) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to listOf( getSummary(23, "Matematyka", 3.0) ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.5, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries ↑): 4,0 + 3,0 → 3,5 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test fun `calc both semesters average when missing summary in second semester`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -886,9 +1123,9 @@ class GradeAverageProviderTest { @Test fun `calc both semesters average when missing summary in first semester`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { gradeRepository.getGrades( @@ -928,9 +1165,9 @@ class GradeAverageProviderTest { @Test fun `force calc full year average when missing summary in first semester`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades( @@ -970,11 +1207,17 @@ class GradeAverageProviderTest { @Test fun `force calc both semesters average with different average from all grades and from two semesters`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - every { preferencesRepository.isOptionalArithmeticAverage } returns false + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -986,7 +1229,13 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -994,16 +1243,26 @@ class GradeAverageProviderTest { ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(5.2296, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → 5.229636363636364 + assertEquals( + 5.2296, + items.single { it.subject == "Fizyka" }.average, + .0001 + ) // (from details): 5.72727272 + 4,732 → 5.229636363636364 } @Test fun `force calc full year average with different average from all grades and from two semesters`() { - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( @@ -1025,58 +1284,31 @@ class GradeAverageProviderTest { ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } - assertEquals(5.5429, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,732 → .average() + assertEquals( + 5.5429, + items.single { it.subject == "Fizyka" }.average, + .0001 + ) // (from details): 5.72727272 + 4,732 → .average() } @Test fun `force calc both semesters average with different average from all grades and from two semesters with custom modifiers`() { val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeMinusModifier } returns .33 - every { preferencesRepository.gradePlusModifier } returns .5 + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.5) - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { semesterRepository.getSemesters(student) } returns semesters - - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { - listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) - } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { - listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) - } - - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } - - assertEquals(5.2636, items.single { it.subject == "Fizyka" }.average, .0001) // (from details): 5.72727272 + 4,8 → 5.26363636 - } - - @Test - fun `force calc full year average with different average from all grades and from two semesters with custom modifiers`() { - val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) - - every { preferencesRepository.gradeAverageForceCalc } returns true - every { preferencesRepository.isOptionalArithmeticAverage } returns false - every { preferencesRepository.gradeMinusModifier } returns .33 - every { preferencesRepository.gradePlusModifier } returns .5 - - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { @@ -1107,6 +1339,65 @@ class GradeAverageProviderTest { ).getResult() } + assertEquals( + 5.2636, + items.single { it.subject == "Fizyka" }.average, + .0001 + ) // (from details): 5.72727272 + 4,8 → 5.26363636 + } + + @Test + fun `force calc full year average with different average from all grades and from two semesters with custom modifiers`() { + val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) + + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) + every { preferencesRepository.gradeMinusModifierFlow } returns flowOf(.33) + every { preferencesRepository.gradePlusModifierFlow } returns flowOf(.5) + + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) + coEvery { semesterRepository.getSemesters(student) } returns semesters + + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + } + + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } + assertEquals( 5.5555, items.single { it.subject == "Fizyka" }.average, @@ -1116,20 +1407,20 @@ class GradeAverageProviderTest { @Test fun `calc both semesters average when both summary have same average from vulcan and second semester has no grades`() { - every { preferencesRepository.gradeAverageForceCalc } returns false - every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - every { preferencesRepository.isOptionalArithmeticAverage } returns false + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns - resourceFlow { firstGrades to firstSummaries } + resourceFlow { firstGrades to firstSummaries } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf() to firstSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( - student, - semesters[2].semesterId, - true + student = student, + semesterId = semesters[2].semesterId, + forceRefresh = true, ).getResult() } From 220395622874c4115f39ff463b77dcfe0c23266f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:25:26 +0000 Subject: [PATCH 150/545] Bump androidx.lifecycle:lifecycle-livedata-ktx from 2.6.0 to 2.6.1 (#2156) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1eddaecca..1d7a3bb82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,7 +214,7 @@ dependencies { implementation "androidx.work:work-runtime-ktx:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" From 597d1d763e96d3523a549adbbdb344cbbe2aca8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:25:50 +0000 Subject: [PATCH 151/545] Bump androidx.activity:activity-ktx from 1.6.1 to 1.7.0 (#2155) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1d7a3bb82..55ef9cb0c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ dependencies { implementation "androidx.core:core-ktx:1.9.0" implementation 'androidx.core:core-splashscreen:1.0.0' - implementation "androidx.activity:activity-ktx:1.6.1" + implementation "androidx.activity:activity-ktx:1.7.0" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.annotation:annotation:1.6.0" From 6398c9a09754bdc443231bc6a1a2844777141e84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:26:39 +0000 Subject: [PATCH 152/545] Bump io.coil-kt:coil from 2.2.2 to 2.3.0 (#2153) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 55ef9cb0c..64414ff1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -236,7 +236,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.2.2" + implementation "io.coil-kt:coil:2.3.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.9.0' From 97a7b34b99abd4d06b444ec2fe7abc4abcea7fdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:27:20 +0000 Subject: [PATCH 153/545] Bump com.google.firebase:firebase-bom from 31.2.3 to 31.4.0 (#2151) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 64414ff1d..cce704dd0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.2.3') + playImplementation platform('com.google.firebase:firebase-bom:31.4.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 9afb38d5e2286a598686344f2f349891dfa9e310 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:33:48 +0000 Subject: [PATCH 154/545] Bump room from 2.5.0 to 2.5.1 (#2150) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index cce704dd0..fdf6fad55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,7 +179,7 @@ huaweiPublish { ext { work_manager = "2.8.0" android_hilt = "1.0.0" - room = "2.5.0" + room = "2.5.1" chucker = "3.5.2" mockk = "1.13.4" coroutines = "1.6.4" From a1b9ae2826947583bb2d00126a7437506560869a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:35:15 +0000 Subject: [PATCH 155/545] Bump work_manager from 2.8.0 to 2.8.1 (#2152) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index fdf6fad55..ce7927bb0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ huaweiPublish { } ext { - work_manager = "2.8.0" + work_manager = "2.8.1" android_hilt = "1.0.0" room = "2.5.1" chucker = "3.5.2" From 9981f458d00f481bcaf17742a02cc0537912a0fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:39:18 +0000 Subject: [PATCH 156/545] Bump androidx.fragment:fragment-ktx from 1.5.5 to 1.5.6 (#2154) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ce7927bb0..3fd11ccc7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.7.0" implementation "androidx.appcompat:appcompat:1.6.1" - implementation "androidx.fragment:fragment-ktx:1.5.5" + implementation "androidx.fragment:fragment-ktx:1.5.6" implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" From 349307b6a3f186d3baa952cc8e1c977c9b6a049d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 29 Mar 2023 09:50:36 +0200 Subject: [PATCH 157/545] Remove deprecated code (#2157) --- .../wulkanowy/services/sync/SyncManager.kt | 12 +++--------- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 18 ++++++++++++++---- gradlew.bat | 15 +++++++++------ 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index c1bed4dd3..e0a136f98 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -4,18 +4,12 @@ import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.O import androidx.core.app.NotificationManagerCompat import androidx.lifecycle.asFlow +import androidx.work.* import androidx.work.BackoffPolicy.EXPONENTIAL -import androidx.work.Constraints -import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy.KEEP -import androidx.work.ExistingPeriodicWorkPolicy.REPLACE -import androidx.work.ExistingWorkPolicy +import androidx.work.ExistingPeriodicWorkPolicy.UPDATE import androidx.work.NetworkType.CONNECTED import androidx.work.NetworkType.UNMETERED -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkInfo -import androidx.work.WorkManager import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -60,7 +54,7 @@ class SyncManager @Inject constructor( val serviceInterval = preferencesRepository.servicesInterval workManager.enqueueUniquePeriodicWork( - SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP, + SyncWorker::class.java.simpleName, if (restart) UPDATE else KEEP, PeriodicWorkRequestBuilder(serviceInterval, MINUTES) .setInitialDelay(10, MINUTES) .setBackoffCriteria(EXPONENTIAL, 30, MINUTES) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36987 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 eE}hFzMW8;nfCwuaNYFMHA(xzAPSZ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f37..e8be595e3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi 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 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From bb7e927065446a9e4b7d6ded4736ec61485fdba3 Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Wed, 29 Mar 2023 22:14:29 +0200 Subject: [PATCH 158/545] Migrate to material3 (#1660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz Co-authored-by: doteq Co-authored-by: Bartosz Bieniek --- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../repositories/PreferencesRepository.kt | 7 - .../data/repositories/TimetableRepository.kt | 6 + .../widgets/TimetableWidgetService.kt | 8 +- .../github/wulkanowy/ui/base/BaseActivity.kt | 15 +- .../wulkanowy/ui/base/BaseDialogFragment.kt | 19 + .../github/wulkanowy/ui/base/ErrorDialog.kt | 45 +-- .../github/wulkanowy/ui/base/ThemeManager.kt | 17 +- .../wulkanowy/ui/modules/Destination.kt | 8 + .../accountdetails/AccountDetailsFragment.kt | 3 +- .../account/accountedit/AccountEditDialog.kt | 18 +- .../accountquick/AccountQuickDialog.kt | 22 +- .../ui/modules/attendance/AttendanceDialog.kt | 24 +- .../modules/attendance/AttendanceFragment.kt | 6 +- .../ui/modules/conference/ConferenceDialog.kt | 24 +- .../ui/modules/dashboard/DashboardFragment.kt | 3 +- .../adapters/DashboardGradesAdapter.kt | 6 +- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 24 +- .../wulkanowy/ui/modules/exam/ExamFragment.kt | 2 +- .../ui/modules/grade/GradeFragment.kt | 3 +- .../grade/details/GradeDetailsAdapter.kt | 8 +- .../grade/details/GradeDetailsDialog.kt | 40 ++- .../grade/summary/GradeSummaryFragment.kt | 5 +- .../ui/modules/homework/HomeworkFragment.kt | 2 +- .../modules/homework/add/HomeworkAddDialog.kt | 19 +- .../details/HomeworkDetailsAdapter.kt | 18 - .../homework/details/HomeworkDetailsDialog.kt | 32 +- .../details/HomeworkDetailsPresenter.kt | 8 - .../history/LuckyNumberHistoryFragment.kt | 2 +- .../LuckyNumberWidgetConfigureActivity.kt | 23 +- .../LuckyNumberWidgetConfigurePresenter.kt | 17 +- .../LuckyNumberWidgetConfigureView.kt | 2 - .../LuckyNumberWidgetProvider.kt | 196 +++++------ .../wulkanowy/ui/modules/main/MainActivity.kt | 38 +- .../ui/modules/main/MainPresenter.kt | 11 - .../wulkanowy/ui/modules/main/MainView.kt | 2 - .../mailboxchooser/MailboxChooserDialog.kt | 20 +- .../message/send/SendMessageActivity.kt | 27 +- .../modules/message/tab/MessageTabAdapter.kt | 15 +- .../token/MobileDeviceTokenDialog.kt | 19 +- .../wulkanowy/ui/modules/note/NoteDialog.kt | 24 +- .../notifications/NotificationsFragment.kt | 4 +- .../SchoolAnnouncementDialog.kt | 27 +- .../notifications/NotificationsFragment.kt | 10 +- .../ui/modules/settings/sync/SyncFragment.kt | 7 +- .../ui/modules/timetable/TimetableAdapter.kt | 8 +- .../ui/modules/timetable/TimetableDialog.kt | 27 +- .../ui/modules/timetable/TimetableFragment.kt | 2 +- .../additional/AdditionalLessonsFragment.kt | 12 +- .../add/AdditionalLessonAddDialog.kt | 20 +- .../completed/CompletedLessonDialog.kt | 26 +- .../completed/CompletedLessonsFragment.kt | 13 +- .../TimetableWidgetConfigureActivity.kt | 27 -- .../TimetableWidgetConfigurePresenter.kt | 19 +- .../TimetableWidgetConfigureView.kt | 2 - .../timetablewidget/TimetableWidgetFactory.kt | 328 ++++++++---------- .../TimetableWidgetProvider.kt | 161 ++++----- .../wulkanowy/utils/LifecycleAwareVariable.kt | 13 +- .../io/github/wulkanowy/utils/RefreshUtils.kt | 5 + .../drawable-night/background_header_note.xml | 5 - .../background_grade_details_rounded.xml | 5 + ...ackground_grade_details_weight_rounded.xml | 5 + ..._dark.xml => background_grade_rounded.xml} | 4 +- ...xml => background_grade_small_rounded.xml} | 6 +- .../res/drawable/background_header_note.xml | 2 +- ... background_luckynumber_widget_button.xml} | 4 +- .../background_luckynumber_widget_dark.xml | 6 - .../background_material_alert_dialog.xml | 26 ++ .../background_timetable_widget_avatar.xml | 6 + .../background_widget_header_timetable.xml | 7 - ...ackground_widget_header_timetable_dark.xml | 7 - .../background_widget_item_timetable.xml | 6 +- .../drawable/background_widget_timetable.xml | 6 +- app/src/main/res/drawable/ic_chevron_left.xml | 13 +- .../main/res/drawable/ic_chevron_right.xml | 13 +- app/src/main/res/drawable/ic_history.xml | 9 + .../main/res/drawable/ic_scale_balance.xml | 7 + .../res/drawable/ic_timetable_widget_swap.xml | 9 + .../main/res/drawable/ic_widget_chevron.png | Bin 130 -> 0 bytes .../main/res/drawable/ic_widget_chevron.xml | 9 + .../img_luckynumber_widget_preview.png | Bin 19550 -> 3702 bytes .../drawable/img_timetable_widget_preview.png | Bin 25538 -> 21111 bytes app/src/main/res/drawable/shape_badge.xml | 9 + .../layout-v31/widget_timetable_preview.xml | 96 +++++ app/src/main/res/layout/activity_main.xml | 19 +- .../main/res/layout/activity_send_message.xml | 24 +- .../main/res/layout/dialog_account_edit.xml | 31 +- .../main/res/layout/dialog_account_quick.xml | 2 +- .../main/res/layout/dialog_additional_add.xml | 115 +++--- .../main/res/layout/dialog_ads_consent.xml | 2 +- app/src/main/res/layout/dialog_attendance.xml | 58 ++-- app/src/main/res/layout/dialog_conference.xml | 68 ++-- app/src/main/res/layout/dialog_exam.xml | 68 ++-- app/src/main/res/layout/dialog_excuse.xml | 2 +- app/src/main/res/layout/dialog_grade.xml | 105 +++--- app/src/main/res/layout/dialog_homework.xml | 97 +++--- .../main/res/layout/dialog_homework_add.xml | 115 +++--- .../res/layout/dialog_lesson_completed.xml | 78 ++--- .../main/res/layout/dialog_mobile_device.xml | 19 +- app/src/main/res/layout/dialog_note.xml | 71 ++-- .../res/layout/dialog_school_announcement.xml | 53 ++- app/src/main/res/layout/dialog_timetable.xml | 95 ++--- .../res/layout/fragment_account_details.xml | 4 +- .../main/res/layout/fragment_attendance.xml | 3 +- .../layout/fragment_attendance_summary.xml | 2 +- .../main/res/layout/fragment_conference.xml | 2 +- .../main/res/layout/fragment_contributor.xml | 2 +- .../main/res/layout/fragment_dashboard.xml | 2 +- app/src/main/res/layout/fragment_exam.xml | 2 +- app/src/main/res/layout/fragment_grade.xml | 3 +- .../res/layout/fragment_grade_details.xml | 2 +- .../res/layout/fragment_grade_statistics.xml | 2 +- .../res/layout/fragment_grade_summary.xml | 2 +- app/src/main/res/layout/fragment_homework.xml | 4 +- .../res/layout/fragment_login_advanced.xml | 14 +- .../main/res/layout/fragment_login_form.xml | 16 +- .../res/layout/fragment_login_recover.xml | 6 +- .../main/res/layout/fragment_login_symbol.xml | 8 +- .../main/res/layout/fragment_logviewer.xml | 2 +- .../main/res/layout/fragment_lucky_number.xml | 4 +- .../layout/fragment_lucky_number_history.xml | 2 +- app/src/main/res/layout/fragment_message.xml | 2 +- .../res/layout/fragment_message_preview.xml | 2 +- .../main/res/layout/fragment_message_tab.xml | 2 +- .../res/layout/fragment_mobile_device.xml | 3 +- app/src/main/res/layout/fragment_note.xml | 2 +- .../layout/fragment_notifications_center.xml | 2 +- app/src/main/res/layout/fragment_school.xml | 2 +- .../layout/fragment_school_announcement.xml | 2 +- .../main/res/layout/fragment_student_info.xml | 2 +- app/src/main/res/layout/fragment_teacher.xml | 2 +- .../main/res/layout/fragment_timetable.xml | 2 +- .../layout/fragment_timetable_additional.xml | 12 +- .../layout/fragment_timetable_completed.xml | 2 +- .../res/layout/item_dashboard_account.xml | 3 +- .../layout/item_dashboard_admin_message.xml | 7 +- .../layout/item_dashboard_announcements.xml | 5 +- .../res/layout/item_dashboard_conferences.xml | 5 +- .../main/res/layout/item_dashboard_exams.xml | 5 +- .../main/res/layout/item_dashboard_grades.xml | 9 +- .../res/layout/item_dashboard_homework.xml | 5 +- .../item_dashboard_horizontal_group.xml | 4 - .../res/layout/item_dashboard_lessons.xml | 8 +- .../main/res/layout/item_grade_details.xml | 5 +- .../layout/item_homework_dialog_details.xml | 38 +- .../layout/item_login_student_select_help.xml | 7 +- .../main/res/layout/item_message_chips.xml | 16 +- .../res/layout/item_notifications_center.xml | 5 +- .../main/res/layout/item_widget_timetable.xml | 173 +++++---- .../res/layout/item_widget_timetable_dark.xml | 114 ------ .../layout/item_widget_timetable_footer.xml | 12 + .../layout/item_widget_timetable_small.xml | 98 ------ .../item_widget_timetable_small_dark.xml | 97 ------ .../res/layout/layout_preference_switch.xml | 5 + .../res/layout/subitem_dashboard_grades.xml | 4 +- .../layout/subitem_dashboard_small_grade.xml | 3 +- .../main/res/layout/widget_luckynumber.xml | 83 ++--- .../res/layout/widget_luckynumber_dark.xml | 67 ---- app/src/main/res/layout/widget_timetable.xml | 168 +++++---- .../main/res/layout/widget_timetable_dark.xml | 99 ------ .../main/res/menu/action_menu_dashboard.xml | 6 +- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- app/src/main/res/values-night-v31/styles.xml | 65 ++++ app/src/main/res/values-night/styles.xml | 57 ++- app/src/main/res/values-v23/styles.xml | 9 +- app/src/main/res/values-v26/styles.xml | 8 - app/src/main/res/values-v27/styles.xml | 16 + app/src/main/res/values-v28/styles.xml | 9 - app/src/main/res/values-v29/styles.xml | 9 - app/src/main/res/values-v31/styles.xml | 60 ++++ app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/colors.xml | 53 ++- .../main/res/values/preferences_defaults.xml | 1 - app/src/main/res/values/preferences_keys.xml | 1 - app/src/main/res/values/strings.xml | 83 +++++ app/src/main/res/values/styles.xml | 49 ++- .../res/xml/provider_widget_lucky_number.xml | 8 +- .../res/xml/provider_widget_timetable.xml | 5 +- 179 files changed, 2019 insertions(+), 2372 deletions(-) delete mode 100644 app/src/main/res/drawable-night/background_header_note.xml create mode 100644 app/src/main/res/drawable/background_grade_details_rounded.xml create mode 100644 app/src/main/res/drawable/background_grade_details_weight_rounded.xml rename app/src/main/res/drawable/{background_widget_timetable_dark.xml => background_grade_rounded.xml} (74%) rename app/src/main/res/drawable/{background_widget_item_timetable_dark.xml => background_grade_small_rounded.xml} (51%) rename app/src/main/res/drawable/{background_luckynumber_widget.xml => background_luckynumber_widget_button.xml} (65%) delete mode 100644 app/src/main/res/drawable/background_luckynumber_widget_dark.xml create mode 100644 app/src/main/res/drawable/background_material_alert_dialog.xml create mode 100644 app/src/main/res/drawable/background_timetable_widget_avatar.xml delete mode 100644 app/src/main/res/drawable/background_widget_header_timetable.xml delete mode 100644 app/src/main/res/drawable/background_widget_header_timetable_dark.xml create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_scale_balance.xml create mode 100644 app/src/main/res/drawable/ic_timetable_widget_swap.xml delete mode 100644 app/src/main/res/drawable/ic_widget_chevron.png create mode 100644 app/src/main/res/drawable/ic_widget_chevron.xml create mode 100644 app/src/main/res/drawable/shape_badge.xml create mode 100644 app/src/main/res/layout-v31/widget_timetable_preview.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_dark.xml create mode 100644 app/src/main/res/layout/item_widget_timetable_footer.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_small.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_small_dark.xml create mode 100644 app/src/main/res/layout/layout_preference_switch.xml delete mode 100644 app/src/main/res/layout/widget_luckynumber_dark.xml delete mode 100644 app/src/main/res/layout/widget_timetable_dark.xml create mode 100644 app/src/main/res/values-night-v31/styles.xml delete mode 100644 app/src/main/res/values-v26/styles.xml create mode 100644 app/src/main/res/values-v27/styles.xml delete mode 100644 app/src/main/res/values-v28/styles.xml delete mode 100644 app/src/main/res/values-v29/styles.xml create mode 100644 app/src/main/res/values-v31/styles.xml diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml index b7b756b9e..9c21d49d9 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3773093b2..174c9a1fc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,7 +72,7 @@ android:name=".ui.modules.message.send.SendMessageActivity" android:configChanges="orientation|screenSize" android:label="@string/send_message_title" - android:theme="@style/WulkanowyTheme.MessageSend" + android:theme="@style/WulkanowyTheme.NoActionBar" android:windowSoftInputMode="adjustResize" /> ) = timetableAdditionalDb.insertAll(additionalList) 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 45cd2b04e..d48556fa6 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,7 +4,6 @@ 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 @@ -24,14 +23,13 @@ class TimetableWidgetService : RemoteViewsService() { @Inject lateinit var semesterRepo: SemesterRepository - @Inject - lateinit var prefRepository: PreferencesRepository - @Inject lateinit var sharedPref: SharedPrefProvider override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { Timber.d("TimetableWidgetFactory created") - return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, prefRepository, sharedPref, applicationContext, intent) + return TimetableWidgetFactory( + timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 075557a5c..7914df81c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -4,9 +4,9 @@ import android.app.ActivityManager import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R @@ -30,6 +30,8 @@ abstract class BaseActivity, VB : ViewBinding> : protected var messageContainer: View? = null + protected var messageAnchor: View? = null + abstract var presenter: T override fun onCreate(savedInstanceState: Bundle?) { @@ -48,6 +50,7 @@ abstract class BaseActivity, VB : ViewBinding> : if (messageContainer != null) { Snackbar.make(messageContainer!!, text, LENGTH_LONG) .setAction(R.string.all_details) { showErrorDetailsDialog(error) } + .apply { messageAnchor?.let { anchorView = it } } .show() } else showMessage(text) } @@ -57,12 +60,15 @@ abstract class BaseActivity, VB : ViewBinding> : } override fun showMessage(text: String) { - if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() - else Toast.makeText(this, text, Toast.LENGTH_LONG).show() + if (messageContainer != null) { + Snackbar.make(messageContainer!!, text, LENGTH_LONG) + .apply { messageAnchor?.let { anchorView = it } } + .show() + } else Toast.makeText(this, text, Toast.LENGTH_LONG).show() } override fun showExpiredDialog() { - AlertDialog.Builder(this) + MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) .setMessage(R.string.main_session_relogin) .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() } @@ -74,6 +80,7 @@ abstract class BaseActivity, VB : ViewBinding> : messageContainer?.let { Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) .setAction(R.string.all_change) { openInternetBrowser(redirectUrl) } + .apply { messageAnchor?.let { anchorView = it } } .show() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 25a53395d..561d181a9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -1,8 +1,14 @@ package io.github.wulkanowy.ui.base +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.Toast +import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding +import com.google.android.material.elevation.SurfaceColors import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.lifecycleAwareVariable import javax.inject.Inject @@ -38,6 +44,19 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + @CallSuper + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + view.setBackgroundColor(SurfaceColors.SURFACE_3.getColor(requireContext())) + } + + @CallSuper + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = binding.root + override fun onResume() { super.onResume() analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index fe0e64697..679d904a3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -4,13 +4,13 @@ import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.os.Bundle +import android.view.View import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog import androidx.core.content.getSystemService import androidx.core.os.bundleOf import androidx.core.view.isGone -import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -20,7 +20,7 @@ import io.github.wulkanowy.utils.* import javax.inject.Inject @AndroidEntryPoint -class ErrorDialog : DialogFragment() { +class ErrorDialog : BaseDialogFragment() { @Inject lateinit var appInfo: AppInfo @@ -28,6 +28,8 @@ class ErrorDialog : DialogFragment() { @Inject lateinit var preferencesRepository: PreferencesRepository + private lateinit var error: Throwable + companion object { private const val ARGUMENT_KEY = "error" @@ -36,32 +38,31 @@ class ErrorDialog : DialogFragment() { } } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val error = requireArguments().serializable(ARGUMENT_KEY) - - val binding = DialogErrorBinding.inflate(layoutInflater) - binding.bindErrorDetails(error) - - return getAlertDialog(binding, error).apply { - enableReportButtonIfErrorIsReportable(error) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + error = requireArguments().serializable(ARGUMENT_KEY) } - private fun getAlertDialog(binding: DialogErrorBinding, error: Throwable): AlertDialog { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return MaterialAlertDialogBuilder(requireContext()).apply { val errorStacktrace = error.stackTraceToString() setTitle(R.string.all_details) - setView(binding.root) + setView(DialogErrorBinding.inflate(layoutInflater).apply { binding = this }.root) setNeutralButton(R.string.about_feedback) { _, _ -> openConfirmDialog { openEmailClient(errorStacktrace) } } setNegativeButton(android.R.string.cancel) { _, _ -> } setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) } - }.create() + }.create().apply { + setOnShowListener { + getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() + } + } } - private fun DialogErrorBinding.bindErrorDetails(error: Throwable) { - return with(this) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { errorDialogHumanizedMessage.text = resources.getErrorString(error) errorDialogErrorMessage.text = error.localizedMessage errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() @@ -70,12 +71,6 @@ class ErrorDialog : DialogFragment() { } } - private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) { - setOnShowListener { - getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() - } - } - private fun copyErrorToClipboard(errorStacktrace: String) { val clip = ClipData.newPlainText("Error details", errorStacktrace) requireActivity().getSystemService()?.setPrimaryClip(clip) @@ -83,7 +78,7 @@ class ErrorDialog : DialogFragment() { } private fun openConfirmDialog(callback: () -> Unit) { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.dialog_error_check_update) .setMessage(R.string.dialog_error_check_update_message) .setNeutralButton(R.string.about_feedback) { _, _ -> callback() } @@ -113,8 +108,4 @@ class ErrorDialog : DialogFragment() { } ) } - - private fun showMessage(text: String) { - Toast.makeText(requireContext(), text, LENGTH_LONG).show() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt index e1c234575..f42f315ce 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt @@ -6,15 +6,14 @@ import android.content.pm.PackageManager.GET_ACTIVITIES import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES +import com.google.android.material.color.DynamicColors import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.AppTheme import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity import javax.inject.Inject import javax.inject.Singleton @@ -28,18 +27,19 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer when (activity) { is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black) is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black) - is SendMessageActivity -> activity.setTheme(R.style.WulkanowyTheme_MessageSend_Black) } } + } else if (activity is TimetableWidgetConfigureActivity || activity is LuckyNumberWidgetConfigureActivity) { + DynamicColors.applyToActivityIfAvailable(activity) } } fun applyDefaultTheme() { AppCompatDelegate.setDefaultNightMode( when (preferencesRepository.appTheme) { - AppTheme.LIGHT -> MODE_NIGHT_NO - AppTheme.DARK, AppTheme.BLACK -> MODE_NIGHT_YES - AppTheme.SYSTEM -> MODE_NIGHT_FOLLOW_SYSTEM + AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + AppTheme.DARK, AppTheme.BLACK -> AppCompatDelegate.MODE_NIGHT_YES + AppTheme.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } ) } @@ -52,7 +52,6 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer .let { it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black - || it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black } @Suppress("DEPRECATION") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index 958be5a72..f0969fac4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.more.MoreFragment @@ -43,6 +44,7 @@ sealed class Destination { SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), SCHOOL_AND_TEACHERS(SchoolAndTeachers), LUCKY_NUMBER(LuckyNumber), + LUCKY_NUMBER_HISTORY(LuckyNumberHistory), MORE(More), MESSAGE(Message), MOBILE_DEVICE(MobileDevice), @@ -118,6 +120,12 @@ sealed class Destination { override val destinationFragment get() = LuckyNumberFragment.newInstance() } + @Serializable + object LuckyNumberHistory : Destination() { + override val destinationType get() = Type.LUCKY_NUMBER_HISTORY + override val destinationFragment get() = LuckyNumberHistoryFragment.newInstance() + } + @Serializable object More : Destination() { override val destinationType get() = Type.MORE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt index 41b97b075..d6bc6154b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf import androidx.core.view.get import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student @@ -114,7 +115,7 @@ class AccountDetailsFragment : override fun showLogoutConfirmDialog() { context?.let { - AlertDialog.Builder(it) + MaterialAlertDialogBuilder(it) .setTitle(R.string.account_logout_student) .setMessage(R.string.account_confirm) .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt index 6e2bc8c44..4229579c0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountedit +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.databinding.DialogAccountEditBinding @@ -31,16 +31,12 @@ class AccountEditDialog : BaseDialogFragment(), Accoun } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAccountEditBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt index d23978f5f..2d2dccec4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountquick +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.DialogAccountQuickBinding @@ -36,19 +36,17 @@ class AccountQuickDialog : BaseDialogFragment(), Acco } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAccountQuickBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root - - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) val studentsWithSemesters = requireArguments() .serializable>(STUDENTS_ARGUMENT_KEY).toList() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index eab24f91d..c0026bee5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.attendance +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogAttendanceBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.descriptionRes -import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class AttendanceDialog : DialogFragment() { - - private var binding: DialogAttendanceBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class AttendanceDialog : BaseDialogFragment() { private lateinit var attendance: Attendance @@ -30,15 +29,14 @@ class AttendanceDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) attendance = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAttendanceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 21f30b046..a73c2606e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -4,10 +4,10 @@ import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Bundle import android.view.* import android.view.View.* -import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance @@ -124,7 +124,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } - attendanceNavContainer.elevation = requireContext().dpToPx(8f) + attendanceNavContainer.elevation = requireContext().dpToPx(3f) } } @@ -228,7 +228,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag override fun showExcuseDialog() { val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context)) - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.attendance_excuse_title) .setView(dialogBinding.root) .setNegativeButton(android.R.string.cancel) { _, _ -> } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt index 7834b6e8b..c532377e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.conference +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.core.view.isVisible -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.databinding.DialogConferenceBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class ConferenceDialog : DialogFragment() { - - private var binding: DialogConferenceBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class ConferenceDialog : BaseDialogFragment() { private lateinit var conference: Conference @@ -30,15 +29,14 @@ class ConferenceDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) conference = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogConferenceBinding.inflate(inflater).also { binding = it }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogConferenceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index cd66d6c2f..ce17c7632 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.FragmentDashboardBinding @@ -148,7 +149,7 @@ class DashboardFragment : BaseFragment(R.layout.fragme val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values) val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } } - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.pref_dashboard_appearance_tiles_title) .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } .setPositiveButton(android.R.string.ok) { dialog, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt index d00df9d41..d821de537 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.dashboard.adapters +import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView @@ -8,6 +9,7 @@ import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor class DashboardGradesAdapter : RecyclerView.Adapter() { @@ -37,7 +39,9 @@ class DashboardGradesAdapter : RecyclerView.Adapter() { private lateinit var exam: Exam @@ -32,15 +31,14 @@ class ExamDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) exam = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogExamBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogExamBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index 3d42bd00e..0123e2340 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -62,7 +62,7 @@ class ExamFragment : BaseFragment(R.layout.fragment_exam), examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } examNextButton.setOnClickListener { presenter.onNextWeek() } - examNavContainer.elevation = requireContext().dpToPx(8f) + examNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index 15df47a19..7ce07eb68 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -141,7 +142,7 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) } .toTypedArray() - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setSingleChoiceItems(choices, selectedIndex) { dialog, which -> presenter.onSemesterSelected(which) dialog.dismiss() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index e5c3bb63e..15b5db031 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.grade.details import android.annotation.SuppressLint +import android.content.res.ColorStateList import android.content.res.Resources import android.view.LayoutInflater import android.view.View @@ -17,9 +18,10 @@ import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding import io.github.wulkanowy.databinding.ItemGradeDetailsBinding import io.github.wulkanowy.ui.base.BaseExpandableAdapter import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString import timber.log.Timber -import java.util.BitSet +import java.util.* import javax.inject.Inject class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() { @@ -203,7 +205,9 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter grade.description diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index a1ef2ec5a..39f72f8bc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -1,22 +1,23 @@ package io.github.wulkanowy.ui.modules.grade.details +import android.app.Dialog +import android.content.res.ColorStateList import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE -import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.DialogGradeBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.* - -class GradeDetailsDialog : DialogFragment() { - - private var binding: DialogGradeBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class GradeDetailsDialog : BaseDialogFragment() { private lateinit var grade: Grade @@ -38,16 +39,15 @@ class GradeDetailsDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) grade = requireArguments().serializable(ARGUMENT_KEY) gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogGradeBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogGradeBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -55,10 +55,9 @@ class GradeDetailsDialog : DialogFragment() { with(binding) { gradeDialogSubject.text = grade.subject - gradeDialogColorAndWeightValue.run { - text = context.getString(R.string.grade_weight_value, grade.weight) - setBackgroundResource(grade.getGradeColor()) - } + gradeDialogWeightValue.text = grade.weight + gradeDialogWeightLayout.backgroundTintList = + ColorStateList.valueOf(requireContext().getCompatColor(grade.getGradeColor())) gradeDialogDateValue.text = grade.date.toFormattedString() gradeDialogColorValue.text = getString(grade.colorStringId) @@ -72,7 +71,12 @@ class GradeDetailsDialog : DialogFragment() { gradeDialogValue.run { text = grade.entry - setBackgroundResource(grade.getBackgroundColor(gradeColorTheme)) + backgroundTintList = ColorStateList.valueOf( + ContextCompat.getColor( + requireContext(), + grade.getBackgroundColor(gradeColorTheme) + ) + ) } gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index 3810902ff..abd0b13c4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -7,6 +7,7 @@ import android.view.View.INVISIBLE import android.view.View.VISIBLE import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.GradeSummary @@ -118,7 +119,7 @@ class GradeSummaryFragment : } override fun showCalculatedAverageHelpDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.grade_summary_calculated_average_help_dialog_title) .setMessage(R.string.grade_summary_calculated_average_help_dialog_message) .setPositiveButton(R.string.all_close) { _, _ -> } @@ -126,7 +127,7 @@ class GradeSummaryFragment : } override fun showFinalAverageHelpDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.grade_summary_final_average_help_dialog_title) .setMessage(R.string.grade_summary_final_average_help_dialog_message) .setPositiveButton(R.string.all_close) { _, _ -> } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index 9d5130e40..0381acf35 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -67,7 +67,7 @@ class HomeworkFragment : BaseFragment(R.layout.fragment openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() } - homeworkNavContainer.elevation = requireContext().dpToPx(8f) + homeworkNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt index c2aff2b13..c51370ea7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.homework.add +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogHomeworkAddBinding @@ -21,20 +21,15 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo @Inject lateinit var presenter: HomeworkAddPresenter - // todo: move it to presenter + //todo: move it to presenter private var date: LocalDate? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkAddBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogHomeworkAddBinding.inflate(inflater).apply { binding = this }.root - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt index e03707a5c..1ad2a0e32 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt @@ -31,14 +31,8 @@ class HomeworkDetailsAdapter @Inject constructor() : attachments = value?.attachments.orEmpty() } - var isHomeworkFullscreen = false - var onAttachmentClickListener: (url: String) -> Unit = {} - var onFullScreenClickListener = {} - - var onFullScreenExitClickListener = {} - var onDeleteClickListener: (homework: Homework) -> Unit = {} override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0 @@ -82,18 +76,6 @@ class HomeworkDetailsAdapter @Inject constructor() : homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString } homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString } homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE - homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE - homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE - homeworkDialogFullScreen.setOnClickListener { - homeworkDialogFullScreen.visibility = GONE - homeworkDialogFullScreenExit.visibility = VISIBLE - onFullScreenClickListener() - } - homeworkDialogFullScreenExit.setOnClickListener { - homeworkDialogFullScreen.visibility = VISIBLE - homeworkDialogFullScreenExit.visibility = GONE - onFullScreenExitClickListener() - } homeworkDialogDelete.setOnClickListener { onDeleteClickListener(homework!!) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 5e2cc65dc..1f9bc881b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -1,14 +1,12 @@ package io.github.wulkanowy.ui.modules.homework.details import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework @@ -43,15 +41,14 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) homework = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -67,26 +64,11 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew homeworkDialogClose.setOnClickListener { dismiss() } } - if (presenter.isHomeworkFullscreen) { - dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) - } else { - dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) - } - with(binding.homeworkDialogRecycler) { layoutManager = LinearLayoutManager(context) adapter = detailsAdapter.apply { onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } - onFullScreenClickListener = { - dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) - presenter.isHomeworkFullscreen = true - } - onFullScreenExitClickListener = { - dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) - presenter.isHomeworkFullscreen = false - } onDeleteClickListener = { homework -> presenter.deleteHomework(homework) } - isHomeworkFullscreen = presenter.isHomeworkFullscreen homework = this@HomeworkDetailsDialog.homework } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index e76df6bd0..84933f06b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -5,7 +5,6 @@ import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter @@ -19,15 +18,8 @@ class HomeworkDetailsPresenter @Inject constructor( studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, private val analytics: AnalyticsHelper, - private val preferencesRepository: PreferencesRepository ) : BasePresenter(errorHandler, studentRepository) { - var isHomeworkFullscreen - get() = preferencesRepository.isHomeworkFullscreen - set(value) { - preferencesRepository.isHomeworkFullscreen = value - } - override fun onAttachView(view: HomeworkDetailsView) { super.onAttachView(view) view.initView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt index 53f06cacd..a78ce5dd2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -61,7 +61,7 @@ class LuckyNumberHistoryFragment : luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() } luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() } - luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(8f) + luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index 024beff85..a2d23e543 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -1,16 +1,12 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.Intent -import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity @@ -41,7 +37,6 @@ class LuckyNumberWidgetConfigureActivity : setContentView( ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root ) - intent.extras.let { presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) } @@ -56,22 +51,6 @@ class LuckyNumberWidgetConfigureActivity : configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - var items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += (getString(R.string.widget_timetable_theme_system)) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { selectedId = selectedStudentId diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index cac648da8..7e53dad06 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -32,20 +31,9 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( fun onItemSelect(student: Student) { selectedStudent = student - view?.showThemeDialog() - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } registerStudent(selectedStudent) } - fun onDismissThemeView() { - view?.finishView() - } - private fun loadData() { resourceFlow { studentRepository.getSavedStudents(false) }.onEach { when (it) { @@ -56,10 +44,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( } ?: -1 when { it.data.isEmpty() -> view?.openLoginView() - it.data.size == 1 -> { - selectedStudent = it.data.single().student - view?.showThemeDialog() - } + it.data.size == 1 -> onItemSelect(it.data.single().student) else -> view?.updateData(it.data, selectedStudentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt index b4556f7ef..df13b993d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -7,8 +7,6 @@ interface LuckyNumberWidgetConfigureView : BaseView { fun initView() - fun showThemeDialog() - fun updateData(data: List, selectedStudentId: Long) fun updateLuckyNumberWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index e03e3e90e..bafb2d7e5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -2,14 +2,12 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget import android.app.PendingIntent import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH import android.appwidget.AppWidgetProvider import android.content.Context import android.content.res.Configuration import android.os.Bundle -import android.view.View.GONE -import android.view.View.VISIBLE +import android.util.TypedValue.COMPLEX_UNIT_SP +import android.view.View import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -17,7 +15,6 @@ import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.toFirstResult @@ -41,16 +38,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { lateinit var sharedPref: SharedPrefProvider companion object { + private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196 - const val LUCKY_NUMBER_PENDING_INTENT_ID = 200 + private const val LUCKY_NUMBER_PENDING_INTENT_ID = 300 + private const val LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID = 301 fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId" - - fun getHeightWidgetKey(appWidgetId: Int) = "lucky_number_widget_height_$appWidgetId" - - fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId" } override fun onUpdate( @@ -59,107 +52,86 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) - appWidgetIds?.forEach { appWidgetId -> - val luckyNumber = - getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - val appIntent = PendingIntent.getActivity( - context, - LUCKY_NUMBER_PENDING_INTENT_ID, - SplashActivity.getStartIntent(context, Destination.LuckyNumber), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - ) - if (luckyNumber is Resource.Error) { - Timber.e("Error loading lucky number for widget", luckyNumber.error) - } + val appIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumber), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - val remoteView = - RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) - .apply { - setTextViewText( - R.id.luckyNumberWidgetNumber, - luckyNumber.dataOrNull?.luckyNumber?.toString() ?: "#" - ) - setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) - } + val historyIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumberHistory), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - setStyles(remoteView, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) + appWidgetIds?.forEach { widgetId -> + val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) + val luckyNumberResource = getLuckyNumber(studentId, widgetId) + val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString() + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + .apply { + setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-") + setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) + setOnClickPendingIntent(R.id.luckyNumberWidgetHistoryButton, historyIntent) + } + + resizeWidget(context, appWidgetManager.getAppWidgetOptions(widgetId), remoteView) + appWidgetManager.updateAppWidget(widgetId, remoteView) + } + } + + override fun onAppWidgetOptionsChanged( + context: Context?, + appWidgetManager: AppWidgetManager?, + appWidgetId: Int, + newOptions: Bundle? + ) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + + if (context == null || newOptions == null || appWidgetManager == null) { + return + } + + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + resizeWidget(context, newOptions, remoteView) + appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteView) + } + + private fun resizeWidget(context: Context, options: Bundle, remoteViews: RemoteViews) { + val (width, height) = options.getWidgetSize(context) + val size = minOf(width, height, LUCKY_NUMBER_WIDGET_MAX_SIZE).toFloat() + resizeWidgetContents(size, remoteViews) + Timber.v("LuckyNumberWidget resized: ${width}x${height} ($size)") + } + + private fun resizeWidgetContents(size: Float, remoteViews: RemoteViews) { + var historyButtonVisibility = View.VISIBLE + var luckyNumberTextSize = 72f + + if (size < 150) { + luckyNumberTextSize = 44f + historyButtonVisibility = View.GONE + } + if (size < 75) { + luckyNumberTextSize = 26f + } + + remoteViews.apply { + setTextViewTextSize(R.id.luckyNumberWidgetValue, COMPLEX_UNIT_SP, luckyNumberTextSize) + setViewVisibility(R.id.luckyNumberWidgetHistoryButton, historyButtonVisibility) } } override fun onDeleted(context: Context?, appWidgetIds: IntArray?) { super.onDeleted(context, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> - with(sharedPref) { - delete(getHeightWidgetKey(appWidgetId)) - delete(getStudentWidgetKey(appWidgetId)) - delete(getThemeWidgetKey(appWidgetId)) - delete(getWidthWidgetKey(appWidgetId)) - } + sharedPref.delete(getStudentWidgetKey(appWidgetId)) } } - override fun onAppWidgetOptionsChanged( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetId: Int, - newOptions: Bundle? - ) { - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) - - val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) - - setStyles(remoteView, appWidgetId, newOptions) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) - } - - private fun setStyles(views: RemoteViews, appWidgetId: Int, options: Bundle? = null) { - val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong( - getWidthWidgetKey(appWidgetId), 74 - ).toInt() - val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong( - getHeightWidgetKey(appWidgetId), 74 - ).toInt() - - with(sharedPref) { - putLong(getWidthWidgetKey(appWidgetId), width.toLong()) - putLong(getHeightWidgetKey(appWidgetId), height.toLong()) - } - - val rows = getCellsForSize(height) - val cols = getCellsForSize(width) - - Timber.d("New lucky number widget measurement: %dx%d", width, height) - Timber.d("Widget size: $cols x $rows") - - when { - 1 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = false) - 1 == cols && 1 < rows -> views.setVisibility(imageTop = true, imageLeft = false) - 1 < cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - 1 == cols && 1 == rows -> views.setVisibility(imageTop = true, imageLeft = false) - 2 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - else -> views.setVisibility(imageTop = false, imageLeft = false, title = true) - } - } - - private fun RemoteViews.setVisibility( - imageTop: Boolean, - imageLeft: Boolean, - title: Boolean = false - ) { - setViewVisibility(R.id.luckyNumberWidgetImageTop, if (imageTop) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetImageLeft, if (imageLeft) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetTitle, if (title) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) - } - - private fun getCellsForSize(size: Int): Int { - var n = 2 - while (74 * n - 30 < size) ++n - return n - 1 - } - private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking { try { val students = studentRepository.getSavedStudents() @@ -181,22 +153,24 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { Resource.Success(null) } } catch (e: Exception) { - if (e.cause !is NoCurrentStudentException) { - Timber.e(e, "An error has occurred in lucky number provider") - } + Timber.e(e, "An error has occurred in lucky number provider") Resource.Error(e) } } - private fun getCorrectLayoutId(appWidgetId: Int, context: Context): Int { - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = - context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + private fun Bundle.getWidgetSize(context: Context): Pair { + val minWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) + val maxWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) + val minHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) + val maxHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) - return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) { - R.layout.widget_luckynumber_dark + val orientation = context.resources.configuration.orientation + val isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT + + return if (isPortrait) { + minWidth to maxHeight } else { - R.layout.widget_luckynumber + maxWidth to minHeight } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 510923766..091080a55 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -2,14 +2,14 @@ package io.github.wulkanowy.ui.modules.main import android.content.Context import android.content.Intent -import android.os.Build.VERSION_CODES.P +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.ViewGroup.MarginLayoutParams import androidx.activity.OnBackPressedCallback import androidx.activity.addCallback -import androidx.core.view.ViewCompat -import androidx.core.view.isVisible +import androidx.core.view.* import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.preference.Preference @@ -90,8 +90,16 @@ class MainActivity : BaseActivity(), MainVie super.onCreate(savedInstanceState) setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) setSupportActionBar(binding.mainToolbar) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.mainAppBar.isLifted = true + } + initializeFragmentContainer() + this.savedInstanceState = savedInstanceState messageContainer = binding.mainMessageContainer + messageAnchor = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) { presenter.onBackPressed() @@ -187,6 +195,17 @@ class MainActivity : BaseActivity(), MainVie } } + private fun initializeFragmentContainer() { + ViewCompat.setOnApplyWindowInsetsListener(binding.mainFragmentContainer) { view, insets -> + val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + + view.updateLayoutParams { + bottomMargin = if (binding.mainBottomNav.isVisible) 0 else bottomInsets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + override fun onPreferenceStartFragment( caller: PreferenceFragmentCompat, pref: Preference @@ -231,20 +250,9 @@ class MainActivity : BaseActivity(), MainVie showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters)) } - override fun showActionBarElevation(show: Boolean) { - ViewCompat.setElevation(binding.mainToolbar, if (show) dpToPx(4f) else 0f) - } - override fun showBottomNavigation(show: Boolean) { binding.mainBottomNav.isVisible = show - - if (appInfo.systemVersion >= P) { - window.navigationBarColor = if (show) { - getThemeAttrColor(android.R.attr.navigationBarColor) - } else { - getThemeAttrColor(R.attr.colorSurface) - } - } + binding.mainFragmentContainer.requestApplyInsets() } override fun openMoreDestination(destination: Destination) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index d51cdac62..ae05ecf22 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -14,9 +14,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.AccountView import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView -import io.github.wulkanowy.ui.modules.grade.GradeView -import io.github.wulkanowy.ui.modules.message.MessageView -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper @@ -100,7 +97,6 @@ class MainPresenter @Inject constructor( fun onViewChange(destinationView: BaseView) { view?.apply { showBottomNavigation(shouldShowBottomNavigation(destinationView)) - showActionBarElevation(shouldShowActionBarElevation(destinationView)) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } currentStackSize?.let { @@ -110,13 +106,6 @@ class MainPresenter @Inject constructor( } } - private fun shouldShowActionBarElevation(destination: BaseView) = when (destination) { - is GradeView, - is MessageView, - is SchoolAndTeachersView -> false - else -> true - } - private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) { is AccountView, is StudentInfoView, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 03f9641d0..62436f3bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -28,8 +28,6 @@ interface MainView : BaseView { fun showAccountPicker(studentWithSemesters: List) - fun showActionBarElevation(show: Boolean) - fun showBottomNavigation(show: Boolean) fun notifyMenuViewReselected() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt index 37f9a19b5..8bd84f2bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.message.mailboxchooser +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.databinding.DialogMailboxChooserBinding @@ -37,19 +37,19 @@ class MailboxChooserDialog : BaseDialogFragment(), } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMailboxChooserBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) presenter.onAttachView( view = this, requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 14f3d718d..28147faed 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.message.send import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.Rect +import android.os.Build import android.os.Bundle import android.text.Spanned import android.view.Menu @@ -12,11 +12,14 @@ import android.view.MenuItem import android.view.TouchDelegate import android.view.View.GONE import android.view.View.VISIBLE +import android.view.ViewGroup import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.core.text.parseAsHtml import androidx.core.text.toHtml +import androidx.core.view.* import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Mailbox @@ -24,8 +27,8 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog -import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.nullableSerializable @@ -99,6 +102,13 @@ class SendMessageActivity : BaseActivity= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.sendAppBar.isLifted = true + } + initializeMessageContainer() + messageContainer = binding.sendMessageContainer formRecipientsData = binding.sendMessageTo.addedChipItems as List @@ -130,6 +140,17 @@ class SendMessageActivity : BaseActivity + val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + + view.updateLayoutParams { + bottomMargin = bottomInsets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + private fun onMessageSubjectChange(text: CharSequence?) { formSubjectValue = text.toString() presenter.onMessageContentChange() @@ -252,7 +273,7 @@ class SendMessageActivity : BaseActivity presenter.restoreMessageParts() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 6df6153c5..9792c7085 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Typeface import android.view.LayoutInflater @@ -68,21 +69,23 @@ class MessageTabAdapter @Inject constructor() : } } + @SuppressLint("PrivateResource") private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) { val item = items[position] as MessageTabDataItem.FilterHeader + val context = holder.binding.root.context with(holder.binding) { - chipMailbox.text = item.selectedMailbox - ?: root.context.getString(R.string.message_chip_all_mailboxes) + chipMailbox.text = + item.selectedMailbox ?: context.getString(R.string.message_chip_all_mailboxes) chipMailbox.chipBackgroundColor = ColorStateList.valueOf( if (item.selectedMailbox == null) { - root.context.getCompatColor(R.color.mtrl_choice_chip_background_color) - } else root.context.getThemeAttrColor(android.R.attr.colorPrimary, 64) + context.getCompatColor(R.color.m3_elevated_chip_background_color) + } else context.getThemeAttrColor(R.attr.colorPrimary, 64) ) chipMailbox.setTextColor( if (item.selectedMailbox == null) { - root.context.getThemeAttrColor(android.R.attr.textColorPrimary) - } else root.context.getThemeAttrColor(android.R.attr.colorPrimary) + context.getThemeAttrColor(R.attr.colorOnSurfaceVariant) + } else context.getThemeAttrColor(R.attr.colorPrimary) ) chipMailbox.setOnClickListener { onMailboxClickListener() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt index eb420a6ae..2cc2a2aa7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt @@ -1,17 +1,17 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token +import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.graphics.BitmapFactory import android.os.Bundle import android.util.Base64 -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.Toast import androidx.core.content.getSystemService +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.MobileDeviceToken @@ -31,17 +31,14 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), fun newInstance() = MobileDeviceTokenDialog() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMobileDeviceBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogMobileDeviceBinding.inflate(inflater).apply { binding = this }.root - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt index e46ab42cc..0592e9243 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt @@ -1,25 +1,24 @@ package io.github.wulkanowy.ui.modules.note import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.databinding.DialogNoteBinding import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class NoteDialog : DialogFragment() { - - private var binding: DialogNoteBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class NoteDialog : BaseDialogFragment() { private lateinit var note: Note @@ -34,15 +33,14 @@ class NoteDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) note = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogNoteBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogNoteBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt index 163ba8cdf..0763d4fa8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.notifications import android.os.Bundle import android.view.View import androidx.activity.result.contract.ActivityResultContracts.RequestPermission -import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.FragmentNotificationsBinding @@ -41,7 +41,7 @@ class NotificationsFragment : } private fun showSettingsDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.notifications_header_title) .setMessage(R.string.notifications_header_description) .setNegativeButton(R.string.notifications_skip) { dialog, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt index e33a48f03..c1c584414 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.schoolannouncement +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.parseUonetHtml import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class SchoolAnnouncementDialog : DialogFragment() { - - private var binding: DialogSchoolAnnouncementBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class SchoolAnnouncementDialog : BaseDialogFragment() { private lateinit var announcement: SchoolAnnouncement @@ -30,15 +29,17 @@ class SchoolAnnouncementDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) announcement = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogSchoolAnnouncementBinding.inflate(inflater).also { binding = it }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogSchoolAnnouncementBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 77a3c6cf4..98ac15739 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -8,13 +8,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.thelittlefireman.appkillermanager.AppKillerManager import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException import dagger.hilt.android.AndroidEntryPoint @@ -149,7 +149,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun showFixSyncDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.pref_notify_fix_sync_issues) .setMessage(R.string.pref_notify_fix_sync_issues_message) .setNegativeButton(android.R.string.cancel) { _, _ -> } @@ -177,7 +177,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationsPermissionDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.notifications_header_title) .setMessage(R.string.notifications_header_description) .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> @@ -191,7 +191,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationPiggyBackPermissionDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.pref_notification_piggyback_popup_title)) .setMessage(getString(R.string.pref_notification_piggyback_popup_description)) .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> @@ -205,7 +205,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationExactAlarmSettings() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.pref_notification_exact_alarm_popup_title)) .setMessage(getString(R.string.pref_notification_exact_alarm_popup_descriptions)) .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 8477e3222..2a804d9f6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity @@ -75,7 +76,11 @@ class SyncFragment : PreferenceFragmentCompat(), } override fun showMessage(text: String) { - (activity as? BaseActivity<*, *>)?.showMessage(text) + Snackbar.make(requireView(), text, Snackbar.LENGTH_LONG) + .apply { + anchorView = requireActivity().findViewById(R.id.main_bottom_nav) + show() + } } override fun showExpiredDialog() { 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 2f0d697fc..d917e7d51 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 @@ -160,7 +160,7 @@ class TimetableAdapter @Inject constructor() : timetableSmallItemDescription.setTextColor( root.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary + if (lesson.canceled) R.attr.colorTimetableCanceled else R.attr.colorTimetableChange ) ) @@ -185,7 +185,7 @@ class TimetableAdapter @Inject constructor() : timetableItemDescription.setTextColor( root.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary + if (lesson.canceled) R.attr.colorTimetableCanceled else R.attr.colorTimetableChange ) ) @@ -228,8 +228,8 @@ class TimetableAdapter @Inject constructor() : } private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorPrimary)) - subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorPrimary)) + numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) + subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) } private fun updateNumberColor(numberView: TextView, lesson: Timetable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index 4f5547d20..e8a853479 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -1,24 +1,24 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint +import android.app.Dialog import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.DialogTimetableBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.* import java.time.Instant -class TimetableDialog : DialogFragment() { - - private var binding: DialogTimetableBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class TimetableDialog : BaseDialogFragment() { private lateinit var lesson: Timetable @@ -33,15 +33,14 @@ class TimetableDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) lesson = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogTimetableBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogTimetableBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -82,12 +81,12 @@ class TimetableDialog : DialogFragment() { if (canceled) { timetableDialogChangesTitle.setTextColor( requireContext().getThemeAttrColor( - R.attr.colorPrimary + R.attr.colorTimetableCanceled ) ) timetableDialogChangesValue.setTextColor( requireContext().getThemeAttrColor( - R.attr.colorPrimary + R.attr.colorTimetableCanceled ) ) } else { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index e95d6f827..ebc16239f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -87,7 +87,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme timetableNavDate.setOnClickListener { presenter.onPickDate() } timetableNextButton.setOnClickListener { presenter.onNextDay() } - timetableNavContainer.elevation = requireContext().dpToPx(8f) + timetableNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt index 043fa1f7d..faa833c20 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.os.Bundle import android.view.View -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.TimetableAdditional @@ -13,11 +13,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -73,7 +69,7 @@ class AdditionalLessonsFragment : openAddAdditionalLessonButton.setOnClickListener { presenter.onAdditionalLessonAddButtonClicked() } - additionalLessonsNavContainer.elevation = requireContext().dpToPx(8f) + additionalLessonsNavContainer.elevation = requireContext().dpToPx(3f) } } @@ -154,7 +150,7 @@ class AdditionalLessonsFragment : } override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.additional_lessons_delete_title)) .setItems( arrayOf( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt index f82d64830..134719979 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.timetable.additional.add +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import dagger.hilt.android.AndroidEntryPoint @@ -29,16 +29,14 @@ class AdditionalLessonAddDialog : BaseDialogFragment fun newInstance() = AdditionalLessonAddDialog() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAdditionalAddBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAdditionalAddBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt index ddd7488e4..d937d4dd0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt @@ -1,19 +1,18 @@ package io.github.wulkanowy.ui.modules.timetable.completed +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.databinding.DialogLessonCompletedBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.serializable -class CompletedLessonDialog : DialogFragment() { - - private var binding: DialogLessonCompletedBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class CompletedLessonDialog : BaseDialogFragment() { private lateinit var completedLesson: CompletedLesson @@ -28,15 +27,16 @@ class CompletedLessonDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) completedLesson = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogLessonCompletedBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogLessonCompletedBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 34a69e6ab..77a7bbd5a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -2,9 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.os.Bundle import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -14,12 +12,7 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -73,7 +66,7 @@ class CompletedLessonsFragment : completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } - completedLessonsNavContainer.elevation = requireContext().dpToPx(8f) + completedLessonsNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 6ef6cfc98..672dbe720 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -2,14 +2,11 @@ package io.github.wulkanowy.ui.modules.timetablewidget import android.appwidget.AppWidgetManager.* import android.content.Intent -import android.os.Build import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity @@ -34,8 +31,6 @@ class TimetableWidgetConfigureActivity : @Inject lateinit var appInfo: AppInfo - private var dialog: AlertDialog? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) @@ -61,23 +56,6 @@ class TimetableWidgetConfigureActivity : configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - var items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - - if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { selectedId = selectedStudentId @@ -110,9 +88,4 @@ class TimetableWidgetConfigureActivity : override fun openLoginView() { startActivity(LoginActivity.getStartIntent(this)) } - - override fun onDestroy() { - super.onDestroy() - dialog?.dismiss() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index dc2a7c6c7..87e89336c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -39,22 +38,9 @@ class TimetableWidgetConfigurePresenter @Inject constructor( fun onItemSelect(student: Student) { selectedStudent = student - - if (isFromProvider) registerStudent(selectedStudent) - else view?.showThemeDialog() - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } registerStudent(selectedStudent) } - fun onDismissThemeView() { - view?.finishView() - } - private fun loadData() { resourceFlow { studentRepository.getSavedStudents(false) }.onEach { when (it) { @@ -65,10 +51,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( } ?: -1 when { it.data.isEmpty() -> view?.openLoginView() - it.data.size == 1 && !isFromProvider -> { - selectedStudent = it.data.single().student - view?.showThemeDialog() - } + it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student) else -> view?.updateData(it.data, selectedStudentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt index accdc28dc..7740b9bbe 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt @@ -11,8 +11,6 @@ interface TimetableWidgetConfigureView : BaseView { fun updateTimetableWidget(widgetId: Int) - fun showThemeDialog() - fun setSuccessResult(widgetId: Int) fun finishView() 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 664086bca..9c5abe1c2 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 @@ -1,27 +1,25 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import android.annotation.SuppressLint import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.graphics.Paint.ANTI_ALIAS_FLAG import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.view.View.GONE import android.view.View.VISIBLE -import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R import io.github.wulkanowy.data.dataOrNull 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.TimetableMode -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.data.toFirstResult -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey @@ -29,13 +27,13 @@ import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber +import java.time.Instant import java.time.LocalDate class TimetableWidgetFactory( private val timetableRepository: TimetableRepository, private val studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, - private val prefRepository: PreferencesRepository, private val sharedPref: SharedPrefProvider, private val context: Context, private val intent: Intent? @@ -43,19 +41,22 @@ class TimetableWidgetFactory( private var lessons = emptyList() - private var savedCurrentTheme: Long? = null - - private var primaryColor: Int? = null + private var timetableCanceledColor: Int? = null private var textColor: Int? = null private var timetableChangeColor: Int? = null + private var lastSyncInstant: Instant? = null + override fun getLoadingView() = null override fun hasStableIds() = true - override fun getCount() = lessons.size + override fun getCount() = when { + lessons.isEmpty() -> 0 + else -> lessons.size + 1 + } override fun getViewTypeCount() = 2 @@ -70,195 +71,170 @@ class TimetableWidgetFactory( val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) - updateTheme(appWidgetId) - lessons = getLessons(date, studentId) - - val todayLastLessonEndTimestamp = lessons.maxOfOrNull { it.end } - if (date == LocalDate.now() && todayLastLessonEndTimestamp != null) { - sharedPref.putLong( - key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), - value = todayLastLessonEndTimestamp.epochSecond, - sync = true - ) - } - } - } - - private fun updateTheme(appWidgetId: Int) { - savedCurrentTheme = sharedPref.getLong(getCurrentThemeWidgetKey(appWidgetId), 0) - - if (savedCurrentTheme == 0L) { - primaryColor = R.color.colorPrimary - textColor = android.R.color.black - timetableChangeColor = R.color.timetable_change_dark - } else { - primaryColor = R.color.colorPrimaryLight - textColor = android.R.color.white - timetableChangeColor = R.color.timetable_change_light - } - } - - private fun getItemLayout(lesson: Timetable): Int { - return when { - prefRepository.showWholeClassPlan == TimetableMode.SMALL_OTHER_GROUP && !lesson.isStudentPlan -> { - if (savedCurrentTheme == 0L) R.layout.item_widget_timetable_small - else R.layout.item_widget_timetable_small_dark - } - savedCurrentTheme == 1L -> R.layout.item_widget_timetable_dark - else -> R.layout.item_widget_timetable - } - } - - private fun getLessons(date: LocalDate, studentId: Long) = try { - runBlocking { - if (!studentRepository.isStudentSaved()) return@runBlocking emptyList() - - val students = studentRepository.getSavedStudents() - val student = students.singleOrNull { it.student.id == studentId }?.student - ?: return@runBlocking emptyList() - - val semester = semesterRepository.getCurrentSemester(student) - timetableRepository.getTimetable(student, semester, date, date, false) - .toFirstResult().dataOrNull?.lessons.orEmpty() - .sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) - .filter { - if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { - it.isStudentPlan - } else true + runCatching { + runBlocking { + val student = getStudent(studentId) ?: return@runBlocking + val semester = semesterRepository.getCurrentSemester(student) + lessons = getLessons(student, semester, date) + lastSyncInstant = + timetableRepository.getLastRefreshTimestamp(semester, date, date) + if (date == LocalDate.now()) { + updateTodayLastLessonEnd(appWidgetId) + } } + }.onFailure { + Timber.e(it, "An error has occurred in timetable widget factory") + } } - } catch (e: Exception) { - Timber.e(e, "An error has occurred in timetable widget factory") - emptyList() } - @SuppressLint("DefaultLocale") + private suspend fun getStudent(studentId: Long): Student? { + val students = studentRepository.getSavedStudents() + return students.singleOrNull { it.student.id == studentId }?.student + } + + private suspend fun getLessons( + student: Student, semester: Semester, date: LocalDate + ): List { + val timetable = timetableRepository.getTimetable(student, semester, date, date, false) + val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty() + return lessons.sortedBy { it.number } + } + + private fun updateTodayLastLessonEnd(appWidgetId: Int) { + val todayLastLessonEnd = lessons.maxOfOrNull { it.end } ?: return + val key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId) + sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) + } + + companion object { + const val TIME_FORMAT_STYLE = "HH:mm" + } + override fun getViewAt(position: Int): RemoteViews? { - if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null - - val lesson = lessons[position] - return RemoteViews(context.packageName, getItemLayout(lesson)).apply { - setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) - setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) - setTextViewText( - R.id.timetableWidgetItemTimeStart, - lesson.start.toFormattedString("HH:mm") - ) - setTextViewText( - R.id.timetableWidgetItemTimeFinish, - lesson.end.toFormattedString("HH:mm") - ) - - updateDescription(this, lesson) - - if (lesson.canceled) { - updateStylesCanceled(this) - } else { - updateStylesNotCanceled(this, lesson) + 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) } + } + val lesson = lessons.getOrNull(position) ?: return null + + 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()) } + + updateTheme() + clearLessonStyles(remoteViews) + + when { + lesson.canceled -> applyCancelledLessonStyles(remoteViews) + lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles( + remoteViews, lesson + ) + } + + return remoteViews } - private fun updateDescription(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { - if (lesson.info.isNotBlank() && !lesson.changes) { - setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) - setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemRoom, GONE) - setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) - } else { - setViewVisibility(R.id.timetableWidgetItemDescription, GONE) - setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + private fun updateTheme() { + when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> { + textColor = android.R.color.white + timetableChangeColor = R.color.timetable_change_dark + timetableCanceledColor = R.color.timetable_canceled_dark + } + + else -> { + textColor = android.R.color.black + timetableChangeColor = R.color.timetable_change_light + timetableCanceledColor = R.color.timetable_canceled_light } } } - private fun updateStylesCanceled(remoteViews: RemoteViews) { - with(remoteViews) { - setInt( - R.id.timetableWidgetItemSubject, "setPaintFlags", - STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG - ) - setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!)) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!)) - setTextColor( - R.id.timetableWidgetItemDescription, - context.getCompatColor(primaryColor!!) - ) - } - } + private fun clearLessonStyles(remoteViews: RemoteViews) { + val defaultTextColor = context.getCompatColor(textColor ?: 0) - private fun updateStylesNotCanceled(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { + remoteViews.apply { setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!)) - setTextColor( - R.id.timetableWidgetItemDescription, - context.getCompatColor(timetableChangeColor!!) - ) - - updateNotCanceledLessonNumberColor(this, lesson) - updateNotCanceledSubjectColor(this, lesson) - - val teacherChange = lesson.teacherOld.isNotBlank() - updateNotCanceledRoom(this, lesson, teacherChange) - updateNotCanceledTeacher(this, lesson, teacherChange) + setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemIcon, GONE) + setViewVisibility(R.id.timetableWidgetItemDescription, GONE) + setTextColor(R.id.timetableWidgetItemNumber, defaultTextColor) + setTextColor(R.id.timetableWidgetItemSubject, defaultTextColor) + setTextColor(R.id.timetableWidgetItemRoom, defaultTextColor) + setTextColor(R.id.timetableWidgetItemTeacher, defaultTextColor) + setTextColor(R.id.timetableWidgetItemDescription, defaultTextColor) } } - private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor( - R.id.timetableWidgetItemNumber, context.getCompatColor( - if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!! - else textColor!! - ) - ) - } + private fun applyCancelledLessonStyles(remoteViews: RemoteViews) { + val cancelledThemeColor = context.getCompatColor(timetableCanceledColor ?: 0) + val strikeThroughPaintFlags = STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG - private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor( - R.id.timetableWidgetItemSubject, context.getCompatColor( - if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!! - else textColor!! - ) - ) - } - - private fun updateNotCanceledRoom( - remoteViews: RemoteViews, - lesson: Timetable, - teacherChange: Boolean - ) { - with(remoteViews) { - if (lesson.room.isNotBlank()) { - setTextViewText( - R.id.timetableWidgetItemRoom, - if (teacherChange) lesson.room - else "${context.getString(R.string.timetable_room)} ${lesson.room}" - ) - - setTextColor( - R.id.timetableWidgetItemRoom, context.getCompatColor( - if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!! - else textColor!! - ) - ) - } else setTextViewText(R.id.timetableWidgetItemRoom, "") + remoteViews.apply { + setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", strikeThroughPaintFlags) + setTextColor(R.id.timetableWidgetItemNumber, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemSubject, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemDescription, cancelledThemeColor) + setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) } } - private fun updateNotCanceledTeacher( - remoteViews: RemoteViews, - lesson: Timetable, - teacherChange: Boolean - ) { - remoteViews.setTextViewText( - R.id.timetableWidgetItemTeacher, - if (teacherChange) lesson.teacher - else "" - ) + private fun applyChangedLessonStyles(remoteViews: RemoteViews, lesson: Timetable) { + val changesTextColor = context.getCompatColor(timetableChangeColor ?: 0) + + remoteViews.apply { + setTextColor(R.id.timetableWidgetItemNumber, changesTextColor) + setTextColor(R.id.timetableWidgetItemDescription, changesTextColor) + setViewVisibility(R.id.timetableWidgetItemIcon, VISIBLE) + setImageViewResource(R.id.timetableWidgetItemIcon, R.drawable.ic_timetable_widget_swap) + } + + if (lesson.subject != lesson.subjectOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemSubject, changesTextColor) + } + + if (lesson.room != lesson.roomOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemRoom, changesTextColor) + } + + if (lesson.teacher != lesson.teacherOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemTeacher, changesTextColor) + } + + if (lesson.info.isNotBlank() && !lesson.changes) { + remoteViews.setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) + } } + + private fun getSynchronizationInfoText(synchronizationInstant: Instant) = + synchronizationInstant.run { + val synchronizationTime = toFormattedString(TIME_FORMAT_STYLE) + val synchronizationDate = toFormattedString() + context.getString( + R.string.widget_timetable_last_synchronization, + synchronizationDate, + synchronizationTime, + ) + } } 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 3ba2ae946..624ca30f4 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,10 +8,10 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.Canvas import android.widget.RemoteViews +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.toBitmap import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider @@ -70,11 +70,6 @@ class TimetableWidgetProvider : BroadcastReceiver() { "timetable_widget_today_last_lesson_end_date_time_$appWidgetId" fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" - - fun getCurrentThemeWidgetKey(appWidgetId: Int) = - "timetable_widget_current_theme_$appWidgetId" } @OptIn(DelicateCoroutinesApi::class) @@ -109,8 +104,7 @@ class TimetableWidgetProvider : BroadcastReceiver() { 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 + sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId ) val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) @@ -122,8 +116,7 @@ class TimetableWidgetProvider : BroadcastReceiver() { } if (!buttonType.isNullOrBlank()) { analytics.logEvent( - "changed_timetable_widget_day", - "button" to buttonType + "changed_timetable_widget_day", "button" to buttonType ) } updateWidget(context, toggledWidgetId, date, student) @@ -137,49 +130,21 @@ class TimetableWidgetProvider : BroadcastReceiver() { with(sharedPref) { delete(getStudentWidgetKey(appWidgetId)) delete(getDateWidgetKey(appWidgetId)) - delete(getThemeWidgetKey(appWidgetId)) - delete(getCurrentThemeWidgetKey(appWidgetId)) } } } private fun updateWidget( - context: Context, - appWidgetId: Int, - date: LocalDate, - student: Student? + context: Context, appWidgetId: Int, date: LocalDate, student: Student? ) { - val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = - context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES - var currentTheme = 0L - var layoutId = R.layout.widget_timetable - - if (savedConfigureTheme == 1L || (savedConfigureTheme == 2L && isSystemDarkMode)) { - currentTheme = 1L - layoutId = R.layout.widget_timetable_dark - } - 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) - //make Intent unique - action = appWidgetId.toString() - } - val accountIntent = PendingIntent.getActivity( - context, - -Int.MAX_VALUE + appWidgetId, - Intent(context, TimetableWidgetConfigureActivity::class.java).apply { - addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - putExtra(EXTRA_FROM_PROVIDER, true) - }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - - ) + 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, @@ -187,56 +152,41 @@ class TimetableWidgetProvider : BroadcastReceiver() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) - val remoteView = RemoteViews(context.packageName, layoutId).apply { + 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, - date.toFormattedString("EEEE, dd.MM").capitalise() - ) - setTextViewText( - R.id.timetableWidgetName, - student?.nickOrName ?: context.getString(R.string.all_no_data) - ) - - student?.let { - setImageViewBitmap(R.id.timetableWidgetAccount, context.createAvatarBitmap(it)) - } - + setTextViewText(R.id.timetableWidgetDate, formattedDate) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetName, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetAccount, accountIntent) setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) } + student?.let { + setupAccountView(context, student, remoteView, appWidgetId) + } + with(sharedPref) { - putLong(getCurrentThemeWidgetKey(appWidgetId), currentTheme) putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) } with(appWidgetManager) { - updateAppWidget(appWidgetId, remoteView) + partiallyUpdateAppWidget(appWidgetId, remoteView) notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) - Timber.d("TimetableWidgetProvider updated") } + + Timber.d("TimetableWidgetProvider updated") } private fun createNavIntent( - context: Context, - code: Int, - appWidgetId: Int, - buttonType: String + context: Context, code: Int, appWidgetId: Int, buttonType: String ) = PendingIntent.getBroadcast( - context, - code, - Intent(context, TimetableWidgetProvider::class.java).apply { + context, code, Intent(context, TimetableWidgetProvider::class.java).apply { action = ACTION_APPWIDGET_UPDATE putExtra(EXTRA_BUTTON_TYPE, buttonType) putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) - }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { @@ -258,31 +208,6 @@ class TimetableWidgetProvider : BroadcastReceiver() { null } - private fun Context.createAvatarBitmap(student: Student): Bitmap { - val avatarColor = if (student.avatarColor == -2937041L) { - getCompatColor(R.color.colorPrimaryLight).toLong() - } else { - student.avatarColor - } - val avatarDrawable = createNameInitialsDrawable(student.nickOrName, avatarColor, 0.5f) - - val avatarBitmap = - if (avatarDrawable.intrinsicWidth <= 0 || avatarDrawable.intrinsicHeight <= 0) { - Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - } else { - Bitmap.createBitmap( - avatarDrawable.intrinsicWidth, - avatarDrawable.intrinsicHeight, - Bitmap.Config.ARGB_8888 - ) - } - - val canvas = Canvas(avatarBitmap) - avatarDrawable.setBounds(0, 0, canvas.width, canvas.height) - avatarDrawable.draw(canvas) - return avatarBitmap - } - private fun getWidgetDefaultDateToLoad(appWidgetId: Int): LocalDate { val lastLessonEndTimestamp = sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0) @@ -299,4 +224,44 @@ class TimetableWidgetProvider : BroadcastReceiver() { todayDate.nextOrSameSchoolDay } } + + private fun setupAccountView( + context: Context, + student: Student, + remoteViews: RemoteViews, + appWidgetId: Int + ) { + val accountInitials = student.nickOrName + .split(" ") + .mapNotNull { it.firstOrNull() }.take(2) + .joinToString(separator = "").uppercase() + + val accountPickerIntent = PendingIntent.getActivity( + context, + -Int.MAX_VALUE + appWidgetId, + Intent(context, TimetableWidgetConfigureActivity::class.java).apply { + addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) + putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + putExtra(EXTRA_FROM_PROVIDER, true) + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + // Create background bitmap + val avatarDrawableResource = R.drawable.background_timetable_widget_avatar + 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()) + toBitmap(avatarSize, avatarSize) + } + remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, backgroundBitmap) + } + + remoteViews.apply { + setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials) + setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerIntent) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt index 032e2d28a..76ce66dc5 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.os.Handler import android.os.Looper import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -29,18 +30,18 @@ class LifecycleAwareVariable : ReadWriteProperty, DefaultL } } -class LifecycleAwareVariableActivity : ReadWriteProperty, +class LifecycleAwareVariableComponent : ReadWriteProperty, DefaultLifecycleObserver { private var _value: T? = null - override fun setValue(thisRef: AppCompatActivity, property: KProperty<*>, value: T) { + override fun setValue(thisRef: LifecycleOwner, property: KProperty<*>, value: T) { thisRef.lifecycle.removeObserver(this) _value = value thisRef.lifecycle.addObserver(this) } - override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>) = _value + override fun getValue(thisRef: LifecycleOwner, property: KProperty<*>) = _value ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") override fun onDestroy(owner: LifecycleOwner) { @@ -53,4 +54,8 @@ class LifecycleAwareVariableActivity : ReadWriteProperty Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() -fun lifecycleAwareVariable() = LifecycleAwareVariableActivity() +@Suppress("unused") +fun DialogFragment.lifecycleAwareVariable() = LifecycleAwareVariableComponent() + +@Suppress("unused") +fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableComponent() diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index 93e67be01..721297513 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -49,4 +49,9 @@ class AutoRefreshHelper @Inject constructor( fun updateLastRefreshTimestamp(key: String) { sharedPref.putLong(key, Instant.now().toEpochMilli()) } + + fun getLastRefreshTimestamp(key: String): Instant { + val refreshTimestampMilli = sharedPref.getLong(key, 0) + return Instant.ofEpochMilli(refreshTimestampMilli) + } } diff --git a/app/src/main/res/drawable-night/background_header_note.xml b/app/src/main/res/drawable-night/background_header_note.xml deleted file mode 100644 index 6b594e7c6..000000000 --- a/app/src/main/res/drawable-night/background_header_note.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_grade_details_rounded.xml b/app/src/main/res/drawable/background_grade_details_rounded.xml new file mode 100644 index 000000000..e24088a0c --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_grade_details_weight_rounded.xml b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml new file mode 100644 index 000000000..4b2109128 --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_timetable_dark.xml b/app/src/main/res/drawable/background_grade_rounded.xml similarity index 74% rename from app/src/main/res/drawable/background_widget_timetable_dark.xml rename to app/src/main/res/drawable/background_grade_rounded.xml index 6fe7d0ab2..52c10c2f4 100644 --- a/app/src/main/res/drawable/background_widget_timetable_dark.xml +++ b/app/src/main/res/drawable/background_grade_rounded.xml @@ -1,5 +1,5 @@ - + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/background_widget_item_timetable_dark.xml b/app/src/main/res/drawable/background_grade_small_rounded.xml similarity index 51% rename from app/src/main/res/drawable/background_widget_item_timetable_dark.xml rename to app/src/main/res/drawable/background_grade_small_rounded.xml index e432a648c..dd50417f7 100644 --- a/app/src/main/res/drawable/background_widget_item_timetable_dark.xml +++ b/app/src/main/res/drawable/background_grade_small_rounded.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/background_header_note.xml b/app/src/main/res/drawable/background_header_note.xml index c21e55c6b..8cf84a1cd 100644 --- a/app/src/main/res/drawable/background_header_note.xml +++ b/app/src/main/res/drawable/background_header_note.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/background_luckynumber_widget.xml b/app/src/main/res/drawable/background_luckynumber_widget_button.xml similarity index 65% rename from app/src/main/res/drawable/background_luckynumber_widget.xml rename to app/src/main/res/drawable/background_luckynumber_widget_button.xml index 367c55275..66b1685f6 100644 --- a/app/src/main/res/drawable/background_luckynumber_widget.xml +++ b/app/src/main/res/drawable/background_luckynumber_widget_button.xml @@ -1,6 +1,6 @@ - - + + diff --git a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml b/app/src/main/res/drawable/background_luckynumber_widget_dark.xml deleted file mode 100644 index cb094b57e..000000000 --- a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_material_alert_dialog.xml b/app/src/main/res/drawable/background_material_alert_dialog.xml new file mode 100644 index 000000000..5ab8a3506 --- /dev/null +++ b/app/src/main/res/drawable/background_material_alert_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_timetable_widget_avatar.xml b/app/src/main/res/drawable/background_timetable_widget_avatar.xml new file mode 100644 index 000000000..7f64c4ebe --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_widget_avatar.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_header_timetable.xml b/app/src/main/res/drawable/background_widget_header_timetable.xml deleted file mode 100644 index 98eec700d..000000000 --- a/app/src/main/res/drawable/background_widget_header_timetable.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_header_timetable_dark.xml b/app/src/main/res/drawable/background_widget_header_timetable_dark.xml deleted file mode 100644 index 616a91279..000000000 --- a/app/src/main/res/drawable/background_widget_header_timetable_dark.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file 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 08854fba2..096357584 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 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/background_widget_timetable.xml b/app/src/main/res/drawable/background_widget_timetable.xml index 2267587d9..b589ad29d 100644 --- a/app/src/main/res/drawable/background_widget_timetable.xml +++ b/app/src/main/res/drawable/background_widget_timetable.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/ic_chevron_left.xml b/app/src/main/res/drawable/ic_chevron_left.xml index ee3ff4be8..4250fae47 100644 --- a/app/src/main/res/drawable/ic_chevron_left.xml +++ b/app/src/main/res/drawable/ic_chevron_left.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml index a6d734973..de5037cf0 100644 --- a/app/src/main/res/drawable/ic_chevron_right.xml +++ b/app/src/main/res/drawable/ic_chevron_right.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 000000000..f20e2094b --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_scale_balance.xml b/app/src/main/res/drawable/ic_scale_balance.xml new file mode 100644 index 000000000..c65467a6c --- /dev/null +++ b/app/src/main/res/drawable/ic_scale_balance.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/ic_timetable_widget_swap.xml b/app/src/main/res/drawable/ic_timetable_widget_swap.xml new file mode 100644 index 000000000..2f91489a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_timetable_widget_swap.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_widget_chevron.png b/app/src/main/res/drawable/ic_widget_chevron.png deleted file mode 100644 index 34345521a5b5f97b7a8bb543c3f86104ac914c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4rT@hhU+WOo?>8N;0f>vadj{7`40nZ^Dlrj zu#^P(1vC7s4-ZV7smH*;py=u17@~1LIYHu2Lnmjug|f0mdKI;Vst026U54FCWD diff --git a/app/src/main/res/drawable/ic_widget_chevron.xml b/app/src/main/res/drawable/ic_widget_chevron.xml new file mode 100644 index 000000000..2c88f8189 --- /dev/null +++ b/app/src/main/res/drawable/ic_widget_chevron.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/img_luckynumber_widget_preview.png b/app/src/main/res/drawable/img_luckynumber_widget_preview.png index 539b0a598422fe833ab445130c55c51373473568..267018550f11c15ad0a1c0ecefc14285a5e23d8b 100644 GIT binary patch literal 3702 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 z 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 af8mmQcV?`ZJC%Wffx*+&&t;ucLK6VSlAzlF diff --git a/app/src/main/res/drawable/img_timetable_widget_preview.png b/app/src/main/res/drawable/img_timetable_widget_preview.png index 8494301878a1bdcb838aee6ee82be8b42c3904cd..a81bcf361e5a22491599f8fb785a07ca1a42c37d 100755 GIT binary patch literal 21111 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 + + diff --git a/app/src/main/res/layout-v31/widget_timetable_preview.xml b/app/src/main/res/layout-v31/widget_timetable_preview.xml new file mode 100644 index 000000000..bc556f501 --- /dev/null +++ b/app/src/main/res/layout-v31/widget_timetable_preview.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 11844e244..d14de50a1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,24 +1,31 @@ - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + app:layout_constraintTop_toBottomOf="@id/main_app_bar" + tools:layout="@layout/fragment_dashboard" /> - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + app:layout_constraintTop_toBottomOf="@id/send_app_bar"> + app:layout_constraintTop_toBottomOf="@id/send_app_bar" /> diff --git a/app/src/main/res/layout/dialog_account_edit.xml b/app/src/main/res/layout/dialog_account_edit.xml index 9f617e440..2ab4ccc6a 100644 --- a/app/src/main/res/layout/dialog_account_edit.xml +++ b/app/src/main/res/layout/dialog_account_edit.xml @@ -1,19 +1,14 @@ - - + android:layout_height="match_parent"> - + android:paddingStart="24dp" + android:paddingEnd="24dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:text="@string/additional_lessons_repeat" + app:layout_constraintTop_toBottomOf="@id/additionalLessonDialogDate" /> + android:hint="@string/all_subject" + app:layout_constraintTop_toBottomOf="@id/additionalLessonDialogEnd"> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_ads_consent.xml b/app/src/main/res/layout/dialog_ads_consent.xml index 816074783..118fb9c1f 100644 --- a/app/src/main/res/layout/dialog_ads_consent.xml +++ b/app/src/main/res/layout/dialog_ads_consent.xml @@ -63,7 +63,7 @@ - - - + android:layout_height="match_parent"> - - + app:layout_constraintEnd_toStartOf="@+id/examDialogClose" /> diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index 94facb232..f47f61088 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -32,43 +32,57 @@ android:id="@+id/gradeDialogValue" android:layout_width="match_parent" android:layout_height="86dp" - android:layout_marginStart="0dp" android:layout_marginEnd="16dp" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_details_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:textColor="@android:color/white" android:textSize="30sp" tools:text="6" /> - + android:background="@drawable/background_grade_details_weight_rounded" + android:backgroundTint="@color/grade_black" + android:gravity="center_horizontal"> + + + + + + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:text="@string/all_no_description" + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> - - diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml index 341cec544..8c6cf0a76 100644 --- a/app/src/main/res/layout/dialog_homework.xml +++ b/app/src/main/res/layout/dialog_homework.xml @@ -1,71 +1,56 @@ - + android:layout_height="wrap_content"> + android:background="@drawable/ic_all_divider" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogRecycler" /> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml index 524f0db0d..e0ff5b749 100644 --- a/app/src/main/res/layout/dialog_homework_add.xml +++ b/app/src/main/res/layout/dialog_homework_add.xml @@ -2,37 +2,35 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport="true" - android:minWidth="300dp" - android:paddingStart="8dp" - android:paddingEnd="8dp"> + android:layout_height="match_parent"> - + android:paddingStart="24dp" + android:paddingEnd="24dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:hint="@string/all_subject" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogDate"> + android:hint="@string/all_teacher" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogSubject"> + android:hint="@string/all_content" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogTeacher"> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml index 500cdb6f3..3a1d3fd00 100644 --- a/app/src/main/res/layout/dialog_lesson_completed.xml +++ b/app/src/main/res/layout/dialog_lesson_completed.xml @@ -1,4 +1,5 @@ + - - - - + - - + - - - - + app:layout_constraintTop_toBottomOf="@id/timetableDialogLessonValue" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/timetableDialogTeacherValue" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/timetableDialogRoomValue" + tools:visibility="visible" /> @@ -106,7 +105,7 @@ diff --git a/app/src/main/res/layout/fragment_login_advanced.xml b/app/src/main/res/layout/fragment_login_advanced.xml index c7acaa70b..43016db4c 100644 --- a/app/src/main/res/layout/fragment_login_advanced.xml +++ b/app/src/main/res/layout/fragment_login_advanced.xml @@ -97,7 +97,7 @@ + android:layout_height="0dp" + android:layout_weight="1"> @@ -173,4 +173,4 @@ app:srcCompat="@drawable/ic_chevron_right" app:tint="?colorPrimary" /> - + diff --git a/app/src/main/res/layout/fragment_timetable_completed.xml b/app/src/main/res/layout/fragment_timetable_completed.xml index e089275d9..8d647ff61 100644 --- a/app/src/main/res/layout/fragment_timetable_completed.xml +++ b/app/src/main/res/layout/fragment_timetable_completed.xml @@ -93,7 +93,7 @@ + android:layout_marginVertical="6dp"> + android:layout_marginVertical="6dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_announcements.xml b/app/src/main/res/layout/item_dashboard_announcements.xml index 19f720884..b9ddb7575 100644 --- a/app/src/main/res/layout/item_dashboard_announcements.xml +++ b/app/src/main/res/layout/item_dashboard_announcements.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_conferences.xml b/app/src/main/res/layout/item_dashboard_conferences.xml index 02d3edfc8..b02b8e18f 100644 --- a/app/src/main/res/layout/item_dashboard_conferences.xml +++ b/app/src/main/res/layout/item_dashboard_conferences.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_exams.xml b/app/src/main/res/layout/item_dashboard_exams.xml index 9cc98d790..84302403c 100644 --- a/app/src/main/res/layout/item_dashboard_exams.xml +++ b/app/src/main/res/layout/item_dashboard_exams.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_grades.xml b/app/src/main/res/layout/item_dashboard_grades.xml index 5cc9ce308..345d8a5e4 100644 --- a/app/src/main/res/layout/item_dashboard_grades.xml +++ b/app/src/main/res/layout/item_dashboard_grades.xml @@ -6,8 +6,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="12dp" android:layout_marginVertical="6dp" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_homework.xml b/app/src/main/res/layout/item_dashboard_homework.xml index 975d66efd..b36afc570 100644 --- a/app/src/main/res/layout/item_dashboard_homework.xml +++ b/app/src/main/res/layout/item_dashboard_homework.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml index 0c59d1ebf..1c9246a19 100644 --- a/app/src/main/res/layout/item_dashboard_horizontal_group.xml +++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml @@ -13,7 +13,6 @@ android:layout_width="0dp" android:layout_height="44dp" android:layout_marginVertical="4dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_message_container" app:layout_constraintHorizontal_chainStyle="spread_inside" @@ -81,7 +80,6 @@ android:layout_height="44dp" android:layout_marginVertical="4dp" android:layout_marginEnd="8dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_attendance_container" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_lucky_container" @@ -154,7 +152,6 @@ android:layout_width="0dp" android:layout_height="44dp" android:layout_marginVertical="4dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container" @@ -221,7 +218,6 @@ android:layout_height="44dp" android:layout_marginVertical="4dp" android:visibility="gone" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container" diff --git a/app/src/main/res/layout/item_dashboard_lessons.xml b/app/src/main/res/layout/item_dashboard_lessons.xml index 9156c1a2f..a40f17f22 100644 --- a/app/src/main/res/layout/item_dashboard_lessons.xml +++ b/app/src/main/res/layout/item_dashboard_lessons.xml @@ -5,11 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="12dp" - android:layout_marginVertical="6dp" - android:clickable="true" - android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:layout_marginVertical="6dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_grade_details.xml b/app/src/main/res/layout/item_grade_details.xml index 2f3bd2de5..6849e929f 100644 --- a/app/src/main/res/layout/item_grade_details.xml +++ b/app/src/main/res/layout/item_grade_details.xml @@ -20,11 +20,12 @@ android:id="@+id/gradeItemValue" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:maxLength="5" android:minWidth="45dp" - android:minHeight="40dp" + android:minHeight="45dp" android:textColor="@android:color/white" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer" 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 9d560ba51..1b1c1d39b 100644 --- a/app/src/main/res/layout/item_homework_dialog_details.xml +++ b/app/src/main/res/layout/item_homework_dialog_details.xml @@ -13,16 +13,17 @@ android:orientation="horizontal"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - - - - diff --git a/app/src/main/res/layout/item_message_chips.xml b/app/src/main/res/layout/item_message_chips.xml index da2e20311..c1f36c4d3 100644 --- a/app/src/main/res/layout/item_message_chips.xml +++ b/app/src/main/res/layout/item_message_chips.xml @@ -21,29 +21,23 @@ + android:text="@string/message_chip_only_unread" /> + android:text="@string/message_chip_only_with_attachments" /> diff --git a/app/src/main/res/layout/item_notifications_center.xml b/app/src/main/res/layout/item_notifications_center.xml index 16a7ae0c0..a2a677488 100644 --- a/app/src/main/res/layout/item_notifications_center.xml +++ b/app/src/main/res/layout/item_notifications_center.xml @@ -5,8 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="8dp" - android:layout_marginTop="12dp" - app:cardElevation="4dp"> + android:layout_marginTop="12dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_widget_timetable.xml b/app/src/main/res/layout/item_widget_timetable.xml index 899d75034..27c9db66c 100644 --- a/app/src/main/res/layout/item_widget_timetable.xml +++ b/app/src/main/res/layout/item_widget_timetable.xml @@ -1,129 +1,112 @@ - + android:textAppearance="?attr/textAppearanceHeadline6" + android:textSize="24sp" + tools:text="1" + tools:textColor="?attr/colorTimetableChange" /> - - - + + android:textAppearance="?attr/textAppearanceBodySmall" + tools:text="08:00" /> + android:layout_marginTop="4dp" + android:textAppearance="?attr/textAppearanceBodySmall" + tools:text="09:45" /> + + + + - - + android:lines="1" + android:textAppearance="?attr/textAppearanceTitleMedium" + tools:text="Programowanie aplikacji mobilnych i desktopowych" /> + + + + + + + + - + + + + + diff --git a/app/src/main/res/layout/item_widget_timetable_dark.xml b/app/src/main/res/layout/item_widget_timetable_dark.xml deleted file mode 100644 index 06233244b..000000000 --- a/app/src/main/res/layout/item_widget_timetable_dark.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_widget_timetable_footer.xml b/app/src/main/res/layout/item_widget_timetable_footer.xml new file mode 100644 index 000000000..ef14da5d9 --- /dev/null +++ b/app/src/main/res/layout/item_widget_timetable_footer.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/item_widget_timetable_small.xml b/app/src/main/res/layout/item_widget_timetable_small.xml deleted file mode 100644 index 1bf4072dc..000000000 --- a/app/src/main/res/layout/item_widget_timetable_small.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_widget_timetable_small_dark.xml b/app/src/main/res/layout/item_widget_timetable_small_dark.xml deleted file mode 100644 index 50bbbd031..000000000 --- a/app/src/main/res/layout/item_widget_timetable_small_dark.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_preference_switch.xml b/app/src/main/res/layout/layout_preference_switch.xml new file mode 100644 index 000000000..c4f8a6c2c --- /dev/null +++ b/app/src/main/res/layout/layout_preference_switch.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/subitem_dashboard_grades.xml b/app/src/main/res/layout/subitem_dashboard_grades.xml index 9354be3d6..c8165b954 100644 --- a/app/src/main/res/layout/subitem_dashboard_grades.xml +++ b/app/src/main/res/layout/subitem_dashboard_grades.xml @@ -23,7 +23,7 @@ android:id="@+id/dashboard_grades_subitem_grade_container" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="2dp" android:layout_marginBottom="6dp" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" @@ -36,4 +36,4 @@ android:visibility="gone" tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml index 986d9602a..6800b72e9 100644 --- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml +++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml @@ -5,7 +5,8 @@ android:layout_width="wrap_content" android:layout_height="20dp" android:layout_marginStart="4dp" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_small_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:maxLength="5" android:minWidth="20dp" diff --git a/app/src/main/res/layout/widget_luckynumber.xml b/app/src/main/res/layout/widget_luckynumber.xml index 360a19708..fc8acc605 100644 --- a/app/src/main/res/layout/widget_luckynumber.xml +++ b/app/src/main/res/layout/widget_luckynumber.xml @@ -1,63 +1,46 @@ - + android:adjustViewBounds="true" + android:importantForAccessibility="no" + android:scaleType="fitCenter" + android:src="@drawable/shape_badge" + android:tint="?attr/colorSurface" + app:tint="?attr/colorSurface" + tools:ignore="UseAppTint" /> - - + android:layout_gravity="center" + android:text="17" + android:textColor="?attr/colorPrimary" + android:textSize="72sp" + android:textStyle="bold" + tools:ignore="HardcodedText" /> - - + + diff --git a/app/src/main/res/layout/widget_luckynumber_dark.xml b/app/src/main/res/layout/widget_luckynumber_dark.xml deleted file mode 100644 index def110de2..000000000 --- a/app/src/main/res/layout/widget_luckynumber_dark.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml index 059bb741f..3abc488e0 100644 --- a/app/src/main/res/layout/widget_timetable.xml +++ b/app/src/main/res/layout/widget_timetable.xml @@ -1,99 +1,115 @@ - - + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingVertical="16dp"> - - - - - - - - + android:layout_marginStart="8dp" + android:layout_weight="1" + android:lines="1" + android:textAppearance="?attr/textAppearanceHeadline5" + tools:text="Pon, 12.05" /> + android:tint="?attr/colorPrimary" + app:tint="?attr/colorPrimary" + tools:ignore="UseAppTint" /> - + android:tint="?attr/colorPrimary" + app:tint="?attr/colorPrimary" + tools:ignore="UseAppTint" /> - + + + + + + + + + android:layout_height="match_parent"> - - + + + + + diff --git a/app/src/main/res/layout/widget_timetable_dark.xml b/app/src/main/res/layout/widget_timetable_dark.xml deleted file mode 100644 index 9c8b8c561..000000000 --- a/app/src/main/res/layout/widget_timetable_dark.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/action_menu_dashboard.xml b/app/src/main/res/menu/action_menu_dashboard.xml index 13565a196..71203d321 100644 --- a/app/src/main/res/menu/action_menu_dashboard.xml +++ b/app/src/main/res/menu/action_menu_dashboard.xml @@ -6,13 +6,13 @@ android:icon="@drawable/ic_settings_notifications" android:orderInCategory="1" android:title="@string/notifications_center_title" - app:iconTint="@color/material_on_surface_emphasis_medium" + app:iconTint="?colorControlNormal" app:showAsAction="ifRoom" /> - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index da1bca126..a8699eec4 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/values-night-v31/styles.xml b/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 000000000..067a43533 --- /dev/null +++ b/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,65 @@ + + + + + + + + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 881d5bd4f..7d2f0cfed 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,20 +1,41 @@ - - - - - - + + diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml index 840f53573..95450ae1a 100644 --- a/app/src/main/res/values-v23/styles.xml +++ b/app/src/main/res/values-v23/styles.xml @@ -3,6 +3,11 @@ - \ No newline at end of file + + + diff --git a/app/src/main/res/values-v26/styles.xml b/app/src/main/res/values-v26/styles.xml deleted file mode 100644 index 3fb0a5dd8..000000000 --- a/app/src/main/res/values-v26/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 000000000..1cbe97911 --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/values-v28/styles.xml b/app/src/main/res/values-v28/styles.xml deleted file mode 100644 index a936566f0..000000000 --- a/app/src/main/res/values-v28/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v29/styles.xml b/app/src/main/res/values-v29/styles.xml deleted file mode 100644 index a936566f0..000000000 --- a/app/src/main/res/values-v29/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml new file mode 100644 index 000000000..cffb284ec --- /dev/null +++ b/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,60 @@ + + + + + + + - - - - + + diff --git a/app/src/main/res/xml/provider_widget_lucky_number.xml b/app/src/main/res/xml/provider_widget_lucky_number.xml index 064f20570..330bd53fe 100644 --- a/app/src/main/res/xml/provider_widget_lucky_number.xml +++ b/app/src/main/res/xml/provider_widget_lucky_number.xml @@ -4,11 +4,13 @@ android:configure="io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity" android:initialLayout="@layout/widget_luckynumber" android:minWidth="110dp" - android:minHeight="40dp" - android:minResizeWidth="40dp" + android:minHeight="110dp" android:minResizeHeight="40dp" android:previewImage="@drawable/img_luckynumber_widget_preview" + android:previewLayout="@layout/widget_luckynumber" android:resizeMode="horizontal|vertical" + android:targetCellWidth="2" + android:targetCellHeight="2" android:updatePeriodMillis="3600000" android:widgetCategory="home_screen" - tools:targetApi="jelly_bean_mr1" /> + tools:targetApi="s" /> diff --git a/app/src/main/res/xml/provider_widget_timetable.xml b/app/src/main/res/xml/provider_widget_timetable.xml index 5392dd50b..3cdad0c81 100644 --- a/app/src/main/res/xml/provider_widget_timetable.xml +++ b/app/src/main/res/xml/provider_widget_timetable.xml @@ -8,7 +8,10 @@ android:minResizeWidth="250dp" android:minResizeHeight="110dp" android:previewImage="@drawable/img_timetable_widget_preview" + android:previewLayout="@layout/widget_timetable_preview" android:resizeMode="horizontal|vertical" + android:targetCellWidth="3" + android:targetCellHeight="2" android:updatePeriodMillis="3600000" android:widgetCategory="home_screen" - tools:targetApi="jelly_bean_mr1" /> + tools:targetApi="s" /> From 8d2d7922f94bf7a15607aaad1bdb49234cb3f610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 29 Mar 2023 22:23:42 +0200 Subject: [PATCH 159/545] Fix collapse garde subject when grade is unread (#2158) --- .../github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 38bae3761..2d63aae4d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -15,6 +15,7 @@ import io.github.wulkanowy.utils.changeModifier import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import javax.inject.Inject @@ -77,7 +78,7 @@ class GradeAverageProvider @Inject constructor( ) } } - } + }.distinctUntilChanged() private fun calculateCombinedAverage( student: Student, From 7aa65e98ce3393a6e5a1e615d5ddc4ef0743a17c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:30:27 +0000 Subject: [PATCH 160/545] Bump com.android.tools:desugar_jdk_libs from 2.0.2 to 2.0.3 (#2162) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3fd11ccc7..e96d9b801 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,7 +188,7 @@ ext { dependencies { implementation "io.github.wulkanowy:sdk:1.9.2" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' + 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-coroutines-android:$coroutines" From cb914fe32b862fe5ab2f6aba597410e0bc2af916 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:30:56 +0000 Subject: [PATCH 161/545] Bump com.google.android.gms:play-services-ads from 21.5.0 to 22.0.0 (#2161) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e96d9b801..a0bc17aa7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -249,7 +249,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:21.5.0' + playImplementation 'com.google.android.gms:play-services-ads:22.0.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.1.300' From a7cf54897ac6d05c042a2b7e39a138d01adccac7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:32:06 +0000 Subject: [PATCH 162/545] Bump kotlin_version from 1.8.10 to 1.8.20 (#2160) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c14e0dbd3..2aa3f758e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.8.10' + kotlin_version = '1.8.20' about_libraries = '10.6.1' hilt_version = "2.45" } From 253e55f70e63e7fe24e75669c01a84a4a3448fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 5 Apr 2023 22:33:01 +0200 Subject: [PATCH 163/545] New Crowdin updates (#2159) --- 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 1897a48b4..bedb491bc 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -682,6 +682,7 @@ Zrušit Žádné lekce + Synchronizováno %1$s v %2$s Vybrat motiv Světlý Tmavý diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 3875b3d98..ebec11b25 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -594,6 +594,7 @@ Cancel No lessons + Synchronized on %1$s at %2$s Choose theme Light Dark diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c03181e48..1e1785bff 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -594,6 +594,7 @@ Cancel Keine Lektionen + Synchronized on %1$s at %2$s Thema wählen Licht Dunkel diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 3875b3d98..ebec11b25 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -594,6 +594,7 @@ Cancel No lessons + Synchronized on %1$s at %2$s Choose theme Light Dark diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7f0c32915..f797a4dcb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -682,6 +682,7 @@ Anuluj Brak lekcji + Zsynchronizowano %1$s o %2$s Wybierz motyw Jasny Ciemny diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 42e1e0bb9..7a42e3880 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -682,6 +682,7 @@ Отменить Нет уроков + Synchronized on %1$s at %2$s Выбрать тему Светлая Тёмная diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 8979a95fc..cde3178bb 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -682,6 +682,7 @@ Zrušiť Žiadne lekcie + Synchronizované %1$s v %2$s Vybrať motív Svetlý Tmavý diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0c7369040..9602aabba 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -682,6 +682,7 @@ Скасувати Немаэ уроків + Synchronized on %1$s at %2$s Увібрати тему Яскрава Темна From c67d2d767d1b037c571efea34787838b92c2fec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 6 Apr 2023 01:28:47 +0200 Subject: [PATCH 164/545] Set error tint to password toggle icon when error occured (#2163) --- .../ui/modules/login/form/LoginFormFragment.kt | 11 +++++------ .../io/github/wulkanowy/utils/ContextExtension.kt | 6 ++---- app/src/main/res/layout/fragment_login_form.xml | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) 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 a0e7608d6..bbc382190 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 @@ -15,12 +15,7 @@ import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData -import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.hideSoftInput -import io.github.wulkanowy.utils.openEmailClient -import io.github.wulkanowy.utils.openInternetBrowser -import io.github.wulkanowy.utils.setOnEditorDoneSignIn -import io.github.wulkanowy.utils.showSoftInput +import io.github.wulkanowy.utils.* import javax.inject.Inject @AndroidEntryPoint @@ -149,12 +144,14 @@ class LoginFormFragment : BaseFragment(R.layout.fragme override fun setErrorPassRequired(focus: Boolean) { with(binding.loginFormPassLayout) { error = getString(R.string.error_field_required) + setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) } } override fun setErrorPassInvalid(focus: Boolean) { with(binding.loginFormPassLayout) { error = getString(R.string.login_invalid_password) + setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) } } @@ -162,6 +159,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme with(binding) { loginFormUsernameLayout.error = " " loginFormPassLayout.error = " " + loginFormPassLayout.setEndIconTintList(requireContext().getAttrColorStateList(R.attr.colorError)) loginFormHostLayout.error = " " loginFormErrorBox.text = message ?: getString(R.string.login_incorrect_password_default) loginFormErrorBox.isVisible = true @@ -181,6 +179,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme override fun clearPassError() { binding.loginFormPassLayout.error = null + binding.loginFormPassLayout.setEndIconTintList(null) binding.loginFormErrorBox.isVisible = false } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index cc4c5aaa4..77f3eb64a 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -6,7 +6,6 @@ import android.content.res.ColorStateList import android.graphics.* import android.text.TextPaint import android.util.DisplayMetrics.DENSITY_DEFAULT -import android.widget.ImageView import androidx.annotation.* import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils @@ -14,7 +13,6 @@ import androidx.core.graphics.applyCanvas import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.toBitmap -import androidx.core.widget.ImageViewCompat @ColorInt @@ -89,6 +87,6 @@ fun Context.createNameInitialsDrawable( .apply { isCircular = true } } -fun ImageView.setTint(@ColorInt color: Int) { - ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color)) +fun Context.getAttrColorStateList(@AttrRes color: Int): ColorStateList { + return ColorStateList.valueOf(getThemeAttrColor(color)) } diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index 1aea70660..fac3960e9 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -180,12 +180,12 @@ android:layout_marginEnd="24dp" android:layout_marginRight="24dp" android:hint="@string/login_password_hint" + app:endIconMode="password_toggle" app:errorEnabled="true" app:errorIconDrawable="@null" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout" - app:passwordToggleEnabled="true"> + app:layout_constraintTop_toBottomOf="@+id/loginFormUsernameLayout"> Date: Thu, 6 Apr 2023 01:29:46 +0200 Subject: [PATCH 165/545] Use segmented toggle buttons instead of option group in grades statistics (#2164) --- .../statistics/GradeStatisticsAdapter.kt | 4 ++- .../layout/item_grade_statistics_header.xml | 26 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index fd0ac5471..3fce8d57e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -116,7 +116,9 @@ class GradeStatisticsAdapter @Inject constructor() : } ) - binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId -> + binding.gradeStatisticsTypeSwitch.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked) return@addOnButtonCheckedListener + currentDataType = when (checkedId) { R.id.gradeStatisticsTypePartial -> GradeStatisticsItem.DataType.PARTIAL R.id.gradeStatisticsTypeSemester -> GradeStatisticsItem.DataType.SEMESTER diff --git a/app/src/main/res/layout/item_grade_statistics_header.xml b/app/src/main/res/layout/item_grade_statistics_header.xml index 92f522ba4..cc35f6066 100644 --- a/app/src/main/res/layout/item_grade_statistics_header.xml +++ b/app/src/main/res/layout/item_grade_statistics_header.xml @@ -1,8 +1,10 @@ + android:layout_height="wrap_content" + tools:context=".ui.modules.grade.statistics.GradeStatisticsAdapter"> - + android:paddingEnd="16dp" + app:selectionRequired="true" + app:singleSelection="true"> - - - - + - \ No newline at end of file + From bce2c39ccc0801e7831ed9936aa76b287fb435eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 6 Apr 2023 10:13:34 +0200 Subject: [PATCH 166/545] Disable error dialog for admin messages (#2165) --- .../wulkanowy/ui/modules/dashboard/DashboardPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 22b0d267e..ac2c896dc 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 @@ -606,7 +606,7 @@ class DashboardPresenter @Inject constructor( } is Resource.Error -> { Timber.i("Loading dashboard admin message result: An exception occurred") - errorHandler.dispatch(it.error) + Timber.e(it.error) updateData( dashboardItem = DashboardItem.AdminMessages( adminMessage = null, @@ -748,7 +748,7 @@ class DashboardPresenter @Inject constructor( itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null val isGeneralError = filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError - val firstError = itemsLoadedList.mapNotNull { it.error }.firstOrNull() + val firstError = itemsLoadedList.firstNotNullOfOrNull { it.error } val filteredOriginalLoadedList = dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT } From 2c9434766861adbc5f7d5d497a4caac12a8c852b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:29:14 +0000 Subject: [PATCH 167/545] Bump androidx.core:core-ktx from 1.9.0 to 1.10.0 (#2167) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a0bc17aa7..edb0ca36d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,7 +193,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.9.0" + implementation "androidx.core:core-ktx:1.10.0" implementation 'androidx.core:core-splashscreen:1.0.0' implementation "androidx.activity:activity-ktx:1.7.0" implementation "androidx.appcompat:appcompat:1.6.1" From 327e61bbdd0c3dbc37da1029c2aad4aae804b444 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:29:50 +0000 Subject: [PATCH 168/545] Bump about_libraries from 10.6.1 to 10.6.2 (#2166) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2aa3f758e..d53632feb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.8.20' - about_libraries = '10.6.1' + about_libraries = '10.6.2' hilt_version = "2.45" } repositories { From 6978ad11ebab55ffb221b060ba82443e9612929d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:11:17 +0000 Subject: [PATCH 169/545] Bump com.google.firebase:firebase-crashlytics-gradle from 2.9.4 to 2.9.5 (#2174) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d53632feb..ad23becdb 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.8.1.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' + 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 "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929" From e7054bb5b94c716f76e997fc4542d56ca981ffef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:11:45 +0000 Subject: [PATCH 170/545] Bump com.google.firebase:firebase-bom from 31.4.0 to 31.5.0 (#2173) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index edb0ca36d..0cc3592f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,7 +242,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.0' implementation 'org.apache.commons:commons-text:1.10.0' - playImplementation platform('com.google.firebase:firebase-bom:31.4.0') + playImplementation platform('com.google.firebase:firebase-bom:31.5.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From de8b38dd9ca1a5ab5b2b4618b8d406031c2e8b19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:12:06 +0000 Subject: [PATCH 171/545] Bump org.robolectric:robolectric from 4.9.2 to 4.10 (#2169) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0cc3592f7..c5c8992c8 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.9.2' + testImplementation 'org.robolectric:robolectric:4.10' testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" From 1f30cc1f902b25cf2b56cc17a8b896d73ec946ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:12:24 +0000 Subject: [PATCH 172/545] Bump mockk from 1.13.4 to 1.13.5 (#2170) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c5c8992c8..d0199a0e7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,7 +181,7 @@ ext { android_hilt = "1.0.0" room = "2.5.1" chucker = "3.5.2" - mockk = "1.13.4" + mockk = "1.13.5" coroutines = "1.6.4" } From 623f0339e692a362a03e202d3767cba8f7b6c9ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 05:21:17 +0000 Subject: [PATCH 173/545] Bump com.fredporciuncula:flow-preferences from 1.9.0 to 1.9.1 (#2172) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d0199a0e7..b8d4e1ca5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -239,7 +239,7 @@ dependencies { implementation "io.coil-kt:coil:2.3.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' - implementation 'com.fredporciuncula:flow-preferences:1.9.0' + 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') From b1d22843b59cbcabca741bdb8cdb9d92c2cc3d9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:29:42 +0000 Subject: [PATCH 174/545] Bump androidx.core:core-splashscreen from 1.0.0 to 1.0.1 (#2180) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b8d4e1ca5..77b3d8c40 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.10.0" - implementation 'androidx.core:core-splashscreen:1.0.0' + implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.7.0" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.6" From b2af5ed57de63a39cd392c3db3c93b91fd7690a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:30:39 +0000 Subject: [PATCH 175/545] Bump com.squareup.okhttp3:logging-interceptor from 4.10.0 to 4.11.0 (#2177) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 77b3d8c40..2a1d58885 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -230,7 +230,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" - implementation "com.squareup.okhttp3:logging-interceptor:4.10.0" + implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" implementation "com.jakewharton.timber:timber:5.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1" From 56d7e94946dca8536764050fdcf0080df10e9cd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:31:46 +0000 Subject: [PATCH 176/545] Bump com.huawei.agconnect:agcp from 1.8.1.300 to 1.9.0.300 (#2179) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ad23becdb..b8813f89f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.4.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.8.1.300' + 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" From 4fedb74005e3a3cb02d6792f7e156ec62149c189 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:32:02 +0000 Subject: [PATCH 177/545] Bump kotlin_version from 1.8.20 to 1.8.21 (#2182) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b8813f89f..3c8552d20 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.8.20' + kotlin_version = '1.8.21' about_libraries = '10.6.2' hilt_version = "2.45" } From f7fa89638a4e0e55ce56b2f7e254816c1b9b82b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:32:49 +0000 Subject: [PATCH 178/545] Bump com.huawei.agconnect:agconnect-crash from 1.8.1.300 to 1.9.0.300 (#2178) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2a1d58885..77a17a293 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -252,7 +252,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:22.0.0' hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.8.1.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 4be66637526c7ef1a77194c87532a52c4dbf4b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:50:34 +0000 Subject: [PATCH 179/545] Bump com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter (#2175) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 77a17a293..732de13c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,7 +229,7 @@ dependencies { implementation "com.github.YarikSOffice:lingver:1.3.0" implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" + implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" implementation "com.jakewharton.timber:timber:5.0.1" From b1a5a77559df78866d7d194771485ec8746557e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 21:50:48 +0000 Subject: [PATCH 180/545] Bump androidx.fragment:fragment-ktx from 1.5.6 to 1.5.7 (#2176) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 732de13c0..f54d80cb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.7.0" implementation "androidx.appcompat:appcompat:1.6.1" - implementation "androidx.fragment:fragment-ktx:1.5.6" + implementation "androidx.fragment:fragment-ktx:1.5.7" implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.preference:preference-ktx:1.2.0" From b195fda026d7d262a3a1c408bcc2fdd11e01527c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 22:02:43 +0000 Subject: [PATCH 181/545] Bump androidx.activity:activity-ktx from 1.7.0 to 1.7.1 (#2181) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f54d80cb9..79cb37ff8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ dependencies { implementation "androidx.core:core-ktx:1.10.0" implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.7.0" + implementation "androidx.activity:activity-ktx:1.7.1" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.5.7" implementation "androidx.annotation:annotation:1.6.0" From f8431d7ad6d10b67594599015bd5f6c9d5a45ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 7 May 2023 23:21:59 +0200 Subject: [PATCH 182/545] SDK update (#2168) --- .gitignore | 1 + app/build.gradle | 2 +- .../io/github/wulkanowy/data/DataModule.kt | 2 - .../github/wulkanowy/data/db/entities/Exam.kt | 1 + .../data/mappers/ConferenceMapper.kt | 6 +- .../wulkanowy/data/mappers/ExamMapper.kt | 2 +- .../wulkanowy/data/mappers/MessageMapper.kt | 2 +- .../data/mappers/MobileDeviceMapper.kt | 2 +- .../data/mappers/RegisterUserMapper.kt | 31 +++---- .../wulkanowy/data/mappers/StudentMapper.kt | 37 -------- .../wulkanowy/data/mappers/TimetableMapper.kt | 16 ++-- .../wulkanowy/data/pojos/RegisterUser.kt | 11 ++- .../data/repositories/AppCreatorRepository.kt | 1 - .../data/repositories/AttendanceRepository.kt | 2 +- .../data/repositories/ExamRepository.kt | 2 +- .../data/repositories/NoteRepository.kt | 2 +- .../data/repositories/SemesterRepository.kt | 2 +- .../data/repositories/StudentRepository.kt | 36 ++++---- .../data/repositories/TeacherRepository.kt | 2 +- .../data/repositories/TimetableRepository.kt | 2 +- .../ui/modules/login/LoginErrorHandler.kt | 14 ++-- .../login/advanced/LoginAdvancedFragment.kt | 8 +- .../login/advanced/LoginAdvancedPresenter.kt | 84 ++++--------------- .../login/advanced/LoginAdvancedView.kt | 1 - .../modules/login/form/LoginFormFragment.kt | 4 + .../modules/login/form/LoginFormPresenter.kt | 3 + .../ui/modules/login/form/LoginFormView.kt | 2 + .../LoginStudentSelectFragment.kt | 1 - .../io/github/wulkanowy/utils/SdkExtension.kt | 11 ++- .../main/res/layout/fragment_login_form.xml | 1 - .../io/github/wulkanowy/TestEnityCreator.kt | 2 +- .../data/mappers/AttendanceMapperKtTest.kt | 4 +- .../repositories/AttendanceRepositoryTest.kt | 12 +-- .../data/repositories/ExamRemoteTest.kt | 13 ++- .../data/repositories/GradeRepositoryTest.kt | 33 ++++++-- .../repositories/MessageRepositoryTest.kt | 2 +- .../MobileDeviceRepositoryTest.kt | 17 ++-- .../repositories/SemesterRepositoryTest.kt | 3 +- .../data/repositories/StudentTest.kt | 81 ------------------ .../repositories/TimetableRepositoryTest.kt | 80 ++++++++++++++---- .../domain/GetMailboxByStudentUseCaseTest.kt | 2 +- .../modules/grade/GradeAverageProviderTest.kt | 2 +- .../login/form/LoginFormPresenterTest.kt | 23 ++++- .../LoginStudentSelectPresenterTest.kt | 16 +++- 44 files changed, 260 insertions(+), 321 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt diff --git a/.gitignore b/.gitignore index cd5ff7146..921bd0a9a 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ Thumbs.db app/src/release/agconnect-services.json app/src/release/agconnect-credentials.json .idea/deploymentTargetDropDown.xml +.idea/kotlinc.xml diff --git a/app/build.gradle b/app/build.gradle index 79cb37ff8..22de4b8b6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:1.9.2" + implementation "io.github.wulkanowy:sdk:14267a9a" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index e538b2b2f..c9e4990f9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -20,7 +20,6 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.RemoteConfigHelper -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -81,7 +80,6 @@ internal class DataModule { .readTimeout(30, TimeUnit.SECONDS) .build() - @OptIn(ExperimentalSerializationApi::class) @Singleton @Provides fun provideRetrofit( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt index 50299e607..2292c3e62 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt @@ -22,6 +22,7 @@ data class Exam( val subject: String, + @Deprecated("not available anymore") val group: String, val type: String, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt index 17a9e5cdb..add6439d4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt @@ -10,9 +10,9 @@ fun List.mapToEntities(semester: Semester) = map { diaryId = semester.diaryId, agenda = it.agenda, conferenceId = it.id, - date = it.dateZoned.toInstant(), + date = it.date.toInstant(), presentOnConference = it.presentOnConference, - subject = it.subject, - title = it.title + subject = it.topic, + title = it.place, ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt index bdb5efbba..173dfebf9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt @@ -11,7 +11,7 @@ fun List.mapToEntities(semester: Semester) = map { date = it.date, entryDate = it.entryDate, subject = it.subject, - group = it.group, + group = "", type = it.type, description = it.description, teacher = it.teacher, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt index 6fc5dc950..a26d76651 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -26,7 +26,7 @@ fun List.mapToEntities( messageId = it.id, correspondents = it.correspondents, subject = it.subject.trim(), - date = it.dateZoned.toInstant(), + date = it.date.toInstant(), folderId = it.folderId, unread = it.unread, unreadBy = it.unreadBy, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt index 1a1c501f6..1f4178fae 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken fun List.mapToEntities(student: Student) = map { MobileDevice( userLoginId = student.userLoginId, - date = it.createDateZoned.toInstant(), + date = it.createDate.toInstant(), deviceId = it.id, name = it.name ) 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 2dfd7e062..bcf26a5e1 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 @@ -3,22 +3,24 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.pojos.* -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.mapper.mapSemesters import java.time.Instant -import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent -import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser +import io.github.wulkanowy.sdk.pojo.RegisterStudent as SdkRegisterStudent +import io.github.wulkanowy.sdk.pojo.RegisterUser as SdkRegisterUser -fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser( +fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser( email = email, login = login, password = password, - baseUrl = baseUrl, + scrapperBaseUrl = scrapperBaseUrl, + loginMode = loginMode, loginType = loginType, symbols = symbols.map { registerSymbol -> RegisterSymbol( symbol = registerSymbol.symbol, error = registerSymbol.error, + hebeBaseUrl = registerSymbol.hebeBaseUrl, + keyId = registerSymbol.keyId, + privatePem = registerSymbol.privatePem, userName = registerSymbol.userName, schools = registerSymbol.schools.map { RegisterUnit( @@ -42,14 +44,13 @@ fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser( classId = registerSubject.classId, isParent = registerSubject.isParent, semesters = registerSubject.semesters - .mapSemesters() .mapToEntities(registerSubject.studentId), ) }, ) } ) - } + }, ) fun RegisterStudent.mapToStudentWithSemesters( @@ -68,17 +69,17 @@ fun RegisterStudent.mapToStudentWithSemesters( classId = classId, studentId = studentId, symbol = symbol.symbol, - loginType = user.loginType.name, + loginType = user.loginType?.name.orEmpty(), schoolName = unit.schoolName, schoolShortName = unit.schoolShortName, schoolSymbol = unit.schoolId, studentName = "$studentName $studentSurname", - loginMode = Sdk.Mode.SCRAPPER.name, - scrapperBaseUrl = user.baseUrl, - mobileBaseUrl = "", - certificateKey = "", - privateKey = "", - password = user.password, + loginMode = user.loginMode.name, + scrapperBaseUrl = user.scrapperBaseUrl.orEmpty(), + mobileBaseUrl = symbol.hebeBaseUrl.orEmpty(), + certificateKey = symbol.keyId.orEmpty(), + privateKey = symbol.privatePem.orEmpty(), + password = user.password.orEmpty(), isCurrent = false, registrationDate = Instant.now(), ).apply { diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt deleted file mode 100644 index a2110d7f5..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/StudentMapper.kt +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.wulkanowy.data.mappers - -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import java.time.Instant -import io.github.wulkanowy.sdk.pojo.Student as SdkStudent - -fun List.mapToEntities(password: String = "", colors: List) = map { - StudentWithSemesters( - student = Student( - email = it.email, - password = password, - isParent = it.isParent, - symbol = it.symbol, - studentId = it.studentId, - userLoginId = it.userLoginId, - userName = it.userName, - studentName = it.studentName + " " + it.studentSurname, - schoolSymbol = it.schoolSymbol, - schoolShortName = it.schoolShortName, - schoolName = it.schoolName, - className = it.className, - classId = it.classId, - scrapperBaseUrl = it.scrapperBaseUrl, - loginType = it.loginType.name, - isCurrent = false, - registrationDate = Instant.now(), - mobileBaseUrl = it.mobileBaseUrl, - privateKey = it.privateKey, - certificateKey = it.certificateKey, - loginMode = it.loginMode.name, - ).apply { - avatarColor = colors.random() - }, - semesters = it.semesters.mapToEntities(it.studentId) - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt index e55aa3cf7..ee525e108 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt @@ -5,10 +5,10 @@ import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.pojos.TimetableFull -import io.github.wulkanowy.sdk.pojo.TimetableFull as SdkTimetableFull +import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetableFull import io.github.wulkanowy.sdk.pojo.TimetableDayHeader as SdkTimetableHeader -import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable -import io.github.wulkanowy.sdk.pojo.TimetableAdditional as SdkTimetableAdditional +import io.github.wulkanowy.sdk.pojo.Lesson as SdkLesson +import io.github.wulkanowy.sdk.pojo.LessonAdditional as SdkTimetableAdditional fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull( lessons = lessons.mapToEntities(semester), @@ -16,13 +16,13 @@ fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull( headers = headers.mapToEntities(semester) ) -fun List.mapToEntities(semester: Semester) = map { +fun List.mapToEntities(semester: Semester) = map { Timetable( studentId = semester.studentId, diaryId = semester.diaryId, number = it.number, - start = it.startZoned.toInstant(), - end = it.endZoned.toInstant(), + start = it.start.toInstant(), + end = it.end.toInstant(), date = it.date, subject = it.subject, subjectOld = it.subjectOld, @@ -45,8 +45,8 @@ fun List.mapToEntities(semester: Semester) = map { diaryId = semester.diaryId, subject = it.subject, date = it.date, - start = it.startZoned.toInstant(), - end = it.endZoned.toInstant(), + start = it.start.toInstant(), + end = it.end.toInstant(), ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt index 4aea33771..98bf1402b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt @@ -1,20 +1,25 @@ package io.github.wulkanowy.data.pojos import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.scrapper.Scrapper data class RegisterUser( val email: String, - val password: String, + val password: String?, val login: String, // may be the same as email - val baseUrl: String, - val loginType: Scrapper.LoginType, + val scrapperBaseUrl: String?, + val loginType: Scrapper.LoginType?, + val loginMode: Sdk.Mode, val symbols: List, ) : java.io.Serializable data class RegisterSymbol( val symbol: String, val error: Throwable?, + val hebeBaseUrl: String?, + val keyId: String?, + val privatePem: String?, val userName: String, val schools: List, ) : java.io.Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt index cbaa12bd3..bec2797db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt @@ -19,7 +19,6 @@ class AppCreatorRepository @Inject constructor( ) { @OptIn(ExperimentalSerializationApi::class) - @Suppress("BlockingMethodInNonBlockingContext") suspend fun getAppCreators() = withContext(dispatchers.io) { val inputStream = context.assets.open("contributors.json").buffered() json.decodeFromStream>(inputStream) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index fd5d8bd16..3afb99077 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -59,7 +59,7 @@ class AttendanceRepository @Inject constructor( } sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getAttendance(start.monday, end.sunday, semester.semesterId) + .getAttendance(start.monday, end.sunday) .mapToEntities(semester, lessons) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index faa80b93e..013c0951d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -52,7 +52,7 @@ class ExamRepository @Inject constructor( fetch = { sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId) + .getExams(start.startExamsDay, start.endExamsDay) .mapToEntities(semester) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index e5d7bc5cb..4101803f3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -42,7 +42,7 @@ class NoteRepository @Inject constructor( fetch = { sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getNotes(semester.semesterId) + .getNotes() .mapToEntities(semester) }, saveFetchResult = { old, new -> 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 96f019223..92bb37081 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 @@ -40,7 +40,7 @@ class SemesterRepository @Inject constructor( val isNoSemesters = semesters.isEmpty() val isRefreshOnModeChangeRequired = when { - Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> { + Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE -> { semesters.firstOrNull { it.isCurrent }?.let { 0 == it.diaryId && 0 == it.kindergartenDiaryId } == true 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 b1d1ba832..4c7069ef6 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 @@ -10,11 +10,9 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.security.decrypt import io.github.wulkanowy.utils.security.encrypt @@ -29,37 +27,35 @@ class StudentRepository @Inject constructor( private val studentDb: StudentDao, private val semesterDb: SemesterDao, private val sdk: Sdk, - private val appInfo: AppInfo, private val appDatabase: AppDatabase ) { - suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty() - suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false suspend fun getStudentsApi( pin: String, symbol: String, token: String - ): List = - sdk.getStudentsFromMobileApi(token, pin, symbol, "") - .mapToEntities(colors = appInfo.defaultColorsForAvatar) + ): RegisterUser = sdk + .getStudentsFromHebe(token, pin, symbol, "") + .mapToPojo(null) suspend fun getStudentsScrapper( email: String, password: String, scrapperBaseUrl: String, symbol: String - ): List = - sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol) - .mapToEntities(password, appInfo.defaultColorsForAvatar) + ): RegisterUser = sdk + .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) + .mapToPojo(password) suspend fun getUserSubjectsFromScrapper( email: String, password: String, scrapperBaseUrl: String, symbol: String - ): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) + ): RegisterUser = sdk + .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol) .mapToPojo(password) suspend fun getStudentsHybrid( @@ -67,15 +63,15 @@ class StudentRepository @Inject constructor( password: String, scrapperBaseUrl: String, symbol: String - ): List = - sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) - .mapToEntities(password, appInfo.defaultColorsForAvatar) + ): RegisterUser = sdk + .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) + .mapToPojo(password) suspend fun getSavedStudents(decryptPass: Boolean = true) = studentDb.loadStudentsWithSemesters() .map { it.apply { - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) } @@ -85,7 +81,7 @@ class StudentRepository @Inject constructor( suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) = studentDb.loadStudentWithSemestersById(id)?.apply { - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) } @@ -95,7 +91,7 @@ class StudentRepository @Inject constructor( suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) } @@ -106,7 +102,7 @@ class StudentRepository @Inject constructor( suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException() - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) } @@ -119,7 +115,7 @@ class StudentRepository @Inject constructor( val students = studentsWithSemesters.map { it.student } .map { it.apply { - if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) { + if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) { password = withContext(dispatchers.io) { encrypt(password, context) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index acd71e1f4..4e3b40f96 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -40,7 +40,7 @@ class TeacherRepository @Inject constructor( fetch = { sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getTeachers(semester.semesterId) + .getTeachers() .mapToEntities(semester) }, saveFetchResult = { old, new -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 26e1f3fff..136fb8d5b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -66,7 +66,7 @@ class TimetableRepository @Inject constructor( fetch = { val timetableFull = sdk.init(student) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getTimetableFull(start.monday, end.sunday) + .getTimetable(start.monday, end.sunday) timetableFull.mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt index 37ab71dce..4f709438a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt @@ -4,13 +4,15 @@ import android.content.Context import android.database.sqlite.SQLiteConstraintException import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R -import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException -import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException -import io.github.wulkanowy.sdk.mobile.exception.InvalidTokenException -import io.github.wulkanowy.sdk.mobile.exception.TokenDeadException +import io.github.wulkanowy.sdk.hebe.exception.InvalidPinException +import io.github.wulkanowy.sdk.hebe.exception.InvalidTokenException +import io.github.wulkanowy.sdk.hebe.exception.TokenDeadException +import io.github.wulkanowy.sdk.hebe.exception.UnknownTokenException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject +import io.github.wulkanowy.sdk.hebe.exception.InvalidSymbolException as InvalidHebeSymbolException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException as InvalidScrapperSymbolException class LoginErrorHandler @Inject constructor( @ApplicationContext context: Context, @@ -32,9 +34,11 @@ class LoginErrorHandler @Inject constructor( is BadCredentialsException -> onBadCredentials(error.message) is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student)) is TokenDeadException -> onInvalidToken(resources.getString(R.string.login_expired_token)) + is UnknownTokenException, is InvalidTokenException -> onInvalidToken(resources.getString(R.string.login_invalid_token)) is InvalidPinException -> onInvalidPin(resources.getString(R.string.login_invalid_pin)) - is InvalidSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol)) + is InvalidScrapperSymbolException, + is InvalidHebeSymbolException -> onInvalidSymbol(resources.getString(R.string.login_invalid_symbol)) else -> super.proceed(error) } } 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 8c90623e1..ead2d71af 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 @@ -34,9 +34,9 @@ class LoginAdvancedFragment : override val formLoginType: String get() = when (binding.loginTypeSwitch.checkedRadioButtonId) { - R.id.loginTypeApi -> "API" - R.id.loginTypeScrapper -> "SCRAPPER" - else -> "HYBRID" + R.id.loginTypeApi -> Sdk.Mode.HEBE.name + R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER.name + else -> Sdk.Mode.HYBRID.name } override val formUsernameValue: String @@ -99,7 +99,7 @@ class LoginAdvancedFragment : loginTypeSwitch.setOnCheckedChangeListener { _, checkedId -> presenter.onLoginModeSelected( when (checkedId) { - R.id.loginTypeApi -> Sdk.Mode.API + R.id.loginTypeApi -> Sdk.Mode.HEBE R.id.loginTypeScrapper -> Sdk.Mode.SCRAPPER else -> Sdk.Mode.HYBRID } 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 33a76e5f9..ab56bd786 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 @@ -1,17 +1,12 @@ package io.github.wulkanowy.ui.modules.login.advanced import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.onResourceNotLoading -import io.github.wulkanowy.data.pojos.RegisterStudent -import io.github.wulkanowy.data.pojos.RegisterSymbol -import io.github.wulkanowy.data.pojos.RegisterUnit import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.scrapper.Scrapper import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData @@ -97,14 +92,16 @@ class LoginAdvancedPresenter @Inject constructor( fun onLoginModeSelected(type: Sdk.Mode) { view?.run { when (type) { - Sdk.Mode.API -> { + Sdk.Mode.HEBE -> { showOnlyMobileApiModeInputs() showMobileApiWarningMessage() } + Sdk.Mode.SCRAPPER -> { showOnlyScrapperModeInputs() showScraperWarningMessage() } + Sdk.Mode.HYBRID -> { showOnlyHybridModeInputs() showHybridWarningMessage() @@ -145,11 +142,12 @@ class LoginAdvancedPresenter @Inject constructor( showProgress(true) showContent(false) } + is Resource.Success -> { analytics.logEvent( "registration_form", "success" to true, - "students" to it.data.size, + "scrapperBaseUrl" to view?.formHostValue.orEmpty(), "error" to "No error" ) val loginData = LoginData( @@ -158,14 +156,15 @@ class LoginAdvancedPresenter @Inject constructor( baseUrl = view?.formHostValue.orEmpty().trim(), symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), ) - when (it.data.size) { + when (it.data.symbols.size) { 0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect( loginData = loginData, - registerUser = it.data.toRegisterUser(loginData), + registerUser = it.data, ) } } + is Resource.Error -> { analytics.logEvent( "registration_form", @@ -183,59 +182,7 @@ class LoginAdvancedPresenter @Inject constructor( }.launch("login") } - private fun List.toRegisterUser(loginData: LoginData) = RegisterUser( - email = loginData.login, - password = loginData.password, - login = loginData.login, - baseUrl = loginData.baseUrl, - loginType = firstOrNull()?.student?.loginType?.let( - Scrapper.LoginType::valueOf - ) ?: Scrapper.LoginType.AUTO, - symbols = this - .groupBy { students -> students.student.symbol } - .map { (symbol, students) -> - RegisterSymbol( - symbol = symbol, - error = null, - userName = "", - schools = students - .groupBy { student -> - Triple( - first = student.student.schoolSymbol, - second = student.student.userLoginId, - third = student.student.schoolShortName - ) - } - .map { (groupKey, students) -> - val (schoolId, loginId, schoolName) = groupKey - RegisterUnit( - students = students.map { - RegisterStudent( - studentId = it.student.studentId, - studentName = it.student.studentName, - studentSecondName = it.student.studentName, - studentSurname = it.student.studentName, - className = it.student.className, - classId = it.student.classId, - isParent = it.student.isParent, - semesters = it.semesters, - ) - }, - userLoginId = loginId, - schoolId = schoolId, - schoolName = schoolName, - schoolShortName = schoolName, - parentIds = listOf(), - studentIds = listOf(), - employeeIds = listOf(), - error = null - ) - } - ) - }, - ) - - private suspend fun getStudentsAppropriatesToLoginType(): List { + private suspend fun getStudentsAppropriatesToLoginType(): RegisterUser { val email = view?.formUsernameValue.orEmpty() val password = view?.formPassValue.orEmpty() val endpoint = view?.formHostValue.orEmpty() @@ -245,10 +192,11 @@ class LoginAdvancedPresenter @Inject constructor( val token = view?.formTokenValue.orEmpty() return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { - Sdk.Mode.API -> studentRepository.getStudentsApi(pin, symbol, token) + Sdk.Mode.HEBE -> studentRepository.getStudentsApi(pin, symbol, token) Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper( email, password, endpoint, symbol ) + Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid( email, password, endpoint, symbol ) @@ -267,8 +215,8 @@ class LoginAdvancedPresenter @Inject constructor( var isCorrect = true - when (Sdk.Mode.valueOf(view?.formLoginType ?: "")) { - Sdk.Mode.API -> { + when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) { + Sdk.Mode.HEBE -> { if (pin.isEmpty()) { view?.setErrorPinRequired() isCorrect = false @@ -284,17 +232,17 @@ class LoginAdvancedPresenter @Inject constructor( isCorrect = false } } + Sdk.Mode.HYBRID, Sdk.Mode.SCRAPPER -> { if (login.isEmpty()) { view?.setErrorUsernameRequired() isCorrect = false } else { - if ("@" in login && "standard" !in host) { + if ("@" in login && "login" in host) { view?.setErrorLoginRequired() isCorrect = false } - - if ("@" !in login && "standard" in host) { + if ("@" !in login && "email" in host) { view?.setErrorEmailRequired() isCorrect = false } 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 824fa0288..34062d938 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 @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.login.advanced -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData 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 bbc382190..43ba3fe1d 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 @@ -204,6 +204,10 @@ class LoginFormFragment : BaseFragment(R.layout.fragme binding.loginFormContainer.visibility = if (show) VISIBLE else GONE } + override fun showOtherOptionsButton(show: Boolean) { + binding.loginFormAdvancedButton.isVisible = show + } + @SuppressLint("SetTextI18n") override fun showVersion() { binding.loginFormVersion.text = "v${appInfo.versionName}" 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 8035ea0ad..ed70eb128 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 @@ -7,6 +7,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.ifNullOrBlank import timber.log.Timber import java.net.URL @@ -15,6 +16,7 @@ import javax.inject.Inject class LoginFormPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, + private val appInfo: AppInfo, private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { @@ -25,6 +27,7 @@ class LoginFormPresenter @Inject constructor( view.run { initView() showContact(false) + showOtherOptionsButton(appInfo.isDebug) showVersion() loginErrorHandler.onBadCredentials = { 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 5a816fb32..e5c680d6f 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 @@ -56,6 +56,8 @@ interface LoginFormView : BaseView { fun showContent(show: Boolean) + fun showOtherOptionsButton(show: Boolean) + fun showVersion() fun navigateToSymbol(loginData: LoginData) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 169702151..c33d12faa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -55,7 +55,6 @@ class LoginStudentSelectFragment : } } - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLoginStudentSelectBinding.bind(view) 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 63a30db8c..481cad113 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -12,18 +12,17 @@ fun Sdk.init(student: Student): Sdk { studentId = student.studentId classId = student.classId - if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { + mobileBaseUrl = student.mobileBaseUrl + } else { scrapperBaseUrl = student.scrapperBaseUrl loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) } - loginId = student.userLoginId mode = Sdk.Mode.valueOf(student.loginMode) mobileBaseUrl = student.mobileBaseUrl - certKey = student.certificateKey - privateKey = student.privateKey - - emptyCookieJarInterceptor = true + keyId = student.certificateKey + privatePem = student.privateKey Timber.d("Sdk in ${student.loginMode} mode reinitialized") diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index fac3960e9..3bfe0c34f 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -247,7 +247,6 @@ android:layout_marginEnd="16dp" android:text="@string/login_advanced" android:textAppearance="?android:textAppearance" - android:visibility="gone" app:backgroundTint="?android:windowBackground" app:fontFamily="sans-serif-medium" app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn" diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index 84a0cb405..c8d95829e 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -48,7 +48,7 @@ fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalD end = end, ) -fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.API) = Student( +fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.HEBE) = Student( scrapperBaseUrl = "http://fakelog.cf", email = "jan@fakelog.cf", certificateKey = "", diff --git a/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt index a35e5d303..ac73becdc 100644 --- a/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/mappers/AttendanceMapperKtTest.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.sdk.pojo.Attendance -import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuse +import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuseStatus import org.junit.Test import java.time.Instant import java.time.LocalDate @@ -98,7 +98,7 @@ class AttendanceMapperTest { timeId = 1, categoryId = 1, deleted = false, - excuseStatus = SentExcuse.Status.WAITING, + excuseStatus = SentExcuseStatus.WAITING, excusable = false, absence = false, excused = false, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index 896491ef0..d0e500f19 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -63,7 +63,7 @@ class AttendanceRepositoryTest { @Test fun `force refresh without difference`() { // prepare - coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList + coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester, emptyList())), flowOf(remoteList.mapToEntities(semester, emptyList())) @@ -77,7 +77,7 @@ class AttendanceRepositoryTest { // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } @@ -86,7 +86,7 @@ class AttendanceRepositoryTest { @Test fun `force refresh with more items in remote`() { // prepare - coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList + coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result @@ -101,7 +101,7 @@ class AttendanceRepositoryTest { // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { @@ -114,7 +114,7 @@ class AttendanceRepositoryTest { @Test fun `force refresh with more items in local`() { // prepare - coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1) + coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList.dropLast(1) coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester, emptyList())), flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result @@ -129,7 +129,7 @@ class AttendanceRepositoryTest { // verify assertEquals(null, res.errorOrNull) assertEquals(1, res.dataOrNull?.size) - coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index e3790662e..fb037a87e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -59,7 +59,7 @@ class ExamRemoteTest { @Test fun `force refresh without difference`() { // prepare - coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList + coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)) @@ -73,7 +73,7 @@ class ExamRemoteTest { // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } coVerify { examDb.deleteAll(match { it.isEmpty() }) } @@ -82,7 +82,7 @@ class ExamRemoteTest { @Test fun `force refresh with more items in remote`() { // prepare - coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList + coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( flowOf(remoteList.dropLast(1).mapToEntities(semester)), flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result @@ -97,7 +97,7 @@ class ExamRemoteTest { // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { @@ -110,7 +110,7 @@ class ExamRemoteTest { @Test fun `force refresh with more items in local`() { // prepare - coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList.dropLast(1) + coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList.dropLast(1) coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result @@ -125,7 +125,7 @@ class ExamRemoteTest { // verify assertEquals(null, res.errorOrNull) assertEquals(1, res.dataOrNull?.size) - coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } coVerify { @@ -137,7 +137,6 @@ class ExamRemoteTest { private fun getExam(date: LocalDate) = SdkExam( subject = "", - group = "", type = "", description = "", teacher = "", 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 e8d0b6c8f..1d6dfaff0 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 @@ -9,13 +9,21 @@ import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Grades import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import java.time.LocalDate @@ -72,7 +80,7 @@ class GradeRepositoryTest { createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") ) - coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(1) } returns createGrades(remoteList) coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( flowOf(listOf()), // empty because it is new user @@ -122,7 +130,7 @@ class GradeRepositoryTest { ), createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") ) - coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(1) } returns createGrades(remoteList) val localList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Jedna ocena"), @@ -169,7 +177,7 @@ class GradeRepositoryTest { createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) - coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(1) } returns createGrades(remoteList) val localList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), @@ -200,7 +208,7 @@ class GradeRepositoryTest { createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), // will be added... createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) - coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(1) } returns createGrades(remoteList) val localList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), @@ -230,7 +238,7 @@ class GradeRepositoryTest { createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") ) - coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(1) } returns createGrades(remoteList) coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( flowOf(listOf()), @@ -250,7 +258,7 @@ class GradeRepositoryTest { fun `force refresh when remote is empty`() { // prepare val remoteList = emptyList() - coEvery { sdk.getGrades(semester.semesterId) } returns (remoteList to emptyList()) + coEvery { sdk.getGrades(semester.semesterId) } returns createGrades(remoteList) val localList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), @@ -284,4 +292,13 @@ class GradeRepositoryTest { weight = weight.toString(), weightValue = weight ) + + private fun createGrades(grades: List): Grades = Grades( + details = grades, + summary = listOf(), + isAverage = false, + isPoints = false, + isForAdults = false, + type = 0, + ) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 9a2c22fd6..3a18ee979 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -224,7 +224,7 @@ class MessageRepositoryTest { recipients = listOf(), subject = "", content = "Test", - dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC), + date = Instant.EPOCH.atZone(ZoneOffset.UTC), folderId = 1, unread = true, readBy = 1, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index 6865aa7da..1a3f96795 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -10,16 +10,21 @@ import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Device import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before import org.junit.Test -import java.time.LocalDateTime.of -import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime.of class MobileDeviceRepositoryTest { @@ -134,9 +139,7 @@ class MobileDeviceRepositoryTest { id = 0, name = "", deviceId = "", - createDate = of(2019, 5, day, 0, 0, 0), - modificationDate = of(2019, 5, day, 0, 0, 0), - createDateZoned = of(2019, 5, day, 0, 0, 0).atZone(ZoneId.systemDefault()), - modificationDateZoned = of(2019, 5, day, 0, 0, 0).atZone(ZoneId.systemDefault()) + createDate = of(2019, 5, day, 0, 0, 0, 0, ZoneOffset.UTC), + modificationDate = of(2019, 5, day, 0, 0, 0, 0, ZoneOffset.UTC), ) } 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 0ed008851..d8256869f 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 @@ -75,7 +75,7 @@ class SemesterRepositoryTest { coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.insertSemesters(any()) } returns listOf() - val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.API.name)) } + val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.HEBE.name)) } assertEquals(2, items.size) assertEquals(0, items[0].diaryId) } @@ -215,6 +215,7 @@ class SemesterRepositoryTest { @Test(expected = RuntimeException::class) fun getCurrentSemester_emptyList() { coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { sdk.getSemesters() } returns emptyList() runBlocking { semesterRepository.getCurrentSemester(student) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt deleted file mode 100644 index 9d3d7a2ec..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.wulkanowy.data.repositories - -import io.github.wulkanowy.TestDispatchersProvider -import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.dao.StudentDao -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Student -import io.github.wulkanowy.utils.AppInfo -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test - -class StudentTest { - - @MockK - private lateinit var mockSdk: Sdk - - @MockK - private lateinit var studentDb: StudentDao - - @MockK - private lateinit var semesterDb: SemesterDao - - private lateinit var studentRepository: StudentRepository - - @Before - fun initApi() { - MockKAnnotations.init(this) - studentRepository = StudentRepository( - context = mockk(), - dispatchers = TestDispatchersProvider(), - studentDb = studentDb, - semesterDb = semesterDb, - sdk = mockSdk, - appInfo = AppInfo(), - appDatabase = mockk() - ) - } - - @Test - fun testRemoteAll() { - coEvery { mockSdk.getStudentsFromScrapper(any(), any(), any(), any()) } returns listOf( - getStudent("test") - ) - - val students = runBlocking { studentRepository.getStudentsScrapper("", "", "http://fakelog.cf", "") } - assertEquals(1, students.size) - assertEquals("test Kowalski", students.first().student.studentName) - } - - private fun getStudent(name: String): Student { - return Student( - email = "", - symbol = "", - studentId = 0, - userLoginId = 0, - userLogin = "", - userName = "", - studentName = name, - studentSurname = "Kowalski", - schoolSymbol = "", - schoolShortName = "", - schoolName = "", - className = "", - classId = 0, - certificateKey = "", - privateKey = "", - loginMode = Sdk.Mode.SCRAPPER, - mobileBaseUrl = "", - loginType = Sdk.ScrapperLoginType.STANDARD, - scrapperBaseUrl = "", - isParent = false, - semesters = emptyList() - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index e56aaa5d0..92ad01b18 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -10,12 +10,17 @@ import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.TimetableFull import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.mockk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -25,7 +30,7 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalDateTime.of import java.time.ZoneId -import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable +import io.github.wulkanowy.sdk.pojo.Lesson as SdkLesson class TimetableRepositoryTest { @@ -62,18 +67,43 @@ class TimetableRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - timetableRepository = TimetableRepository(timetableDb, timetableAdditionalDao, timetableHeaderDao, sdk, timetableNotificationSchedulerHelper, refreshHelper) + timetableRepository = TimetableRepository( + timetableDb, + timetableAdditionalDao, + timetableHeaderDao, + sdk, + timetableNotificationSchedulerHelper, + refreshHelper + ) } @Test fun `force refresh without difference`() { val remoteList = listOf( - createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "123", "Język polski", "Jan Kowalski", false), - createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "124", "Język niemiecki", "Joanna Czarniecka", true) + createTimetableRemote( + start = of(2021, 1, 4, 8, 0), + number = 1, + room = "123", + subject = "Język polski", + teacher = "Jan Kowalski", + changes = false + ), + createTimetableRemote( + start = of(2021, 1, 4, 8, 50), + number = 2, + room = "124", + subject = "Język niemiecki", + teacher = "Joanna Czarniecka", + changes = true + ) ) // prepare - coEvery { sdk.getTimetableFull(startDate, endDate) } returns TimetableFull(emptyList(), remoteList, emptyList()) + coEvery { sdk.getTimetable(startDate, endDate) } returns mockk { + every { headers } returns emptyList() + every { lessons } returns remoteList + every { additional } returns emptyList() + } coEvery { timetableDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)) @@ -81,7 +111,14 @@ class TimetableRepositoryTest { coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { timetableDb.deleteAll(any()) } just Runs - coEvery { timetableAdditionalDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) + coEvery { + timetableAdditionalDao.loadAll( + diaryId = 1, + studentId = 1, + from = startDate, + end = endDate + ) + } returns flowOf(listOf()) coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3) @@ -90,23 +127,36 @@ class TimetableRepositoryTest { coEvery { timetableHeaderDao.deleteAll(emptyList()) } just Runs // execute - val res = runBlocking { timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() } + val res = runBlocking { + timetableRepository.getTimetable( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true + ).toFirstResult() + } // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull!!.lessons.size) - coVerify { sdk.getTimetableFull(startDate, endDate) } + coVerify { sdk.getTimetable(startDate, endDate) } coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } coVerify { timetableDb.insertAll(match { it.isEmpty() }) } coVerify { timetableDb.deleteAll(match { it.isEmpty() }) } } - private fun createTimetableRemote(start: LocalDateTime, number: Int = 1, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false) = SdkTimetable( + private fun createTimetableRemote( + start: LocalDateTime, + number: Int = 1, + room: String = "", + subject: String = "", + teacher: String = "", + changes: Boolean = false + ) = SdkLesson( number = number, - start = start, - end = start.plusMinutes(45), - startZoned = start.atZone(ZoneId.systemDefault()), - endZoned = start.plusMinutes(45).atZone(ZoneId.systemDefault()), + start = start.atZone(ZoneId.systemDefault()), + end = start.plusMinutes(45).atZone(ZoneId.systemDefault()), date = start.toLocalDate(), subject = subject, group = "", 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 6db16d2f5..f58a5381d 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -207,7 +207,7 @@ class GetMailboxByStudentUseCaseTest { className = "", isCurrent = false, isParent = false, - loginMode = Sdk.Mode.API.name, + loginMode = Sdk.Mode.HEBE.name, loginType = Sdk.ScrapperLoginType.STANDARD.name, mobileBaseUrl = "", password = "", 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 10c84efcf..b94002c0d 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 @@ -456,7 +456,7 @@ class GradeAverageProviderTest { @Test fun `force calc current semester average with custom modifiers in api mode`() { - val student = student.copy(loginMode = Sdk.Mode.API.name) + val student = student.copy(loginMode = Sdk.Mode.HEBE.name) every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) 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 bf2d9f2cc..eb1f53006 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 @@ -3,11 +3,18 @@ package io.github.wulkanowy.ui.modules.login.form import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.scrapper.Scrapper import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.mockk.* +import io.github.wulkanowy.utils.AppInfo +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test @@ -30,13 +37,17 @@ class LoginFormPresenterTest { @MockK(relaxed = true) lateinit var analytics: AnalyticsHelper + @MockK + lateinit var appInfo: AppInfo + private lateinit var presenter: LoginFormPresenter private val registerUser = RegisterUser( email = "", password = "", login = "", - baseUrl = "", + scrapperBaseUrl = "", + loginMode = Sdk.Mode.HEBE, loginType = Scrapper.LoginType.AUTO, symbols = listOf(), ) @@ -54,8 +65,14 @@ class LoginFormPresenterTest { every { loginFormView.setErrorPassInvalid(any()) } just Runs every { loginFormView.setErrorPassRequired(any()) } just Runs every { loginFormView.setErrorUsernameRequired() } just Runs + every { appInfo.isDebug } returns false - presenter = LoginFormPresenter(repository, errorHandler, analytics) + presenter = LoginFormPresenter( + studentRepository = repository, + loginErrorHandler = errorHandler, + appInfo = appInfo, + analytics = analytics, + ) presenter.onAttachView(loginFormView) } 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 cf426a50b..da292c51c 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 @@ -6,14 +6,22 @@ import io.github.wulkanowy.data.pojos.RegisterSymbol import io.github.wulkanowy.data.pojos.RegisterUnit import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.scrapper.Scrapper import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.slot +import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test @@ -76,6 +84,9 @@ class LoginStudentSelectPresenterTest { symbol = "", error = null, userName = "", + keyId = null, + privatePem = null, + hebeBaseUrl = null, schools = listOf(school), ) @@ -83,7 +94,8 @@ class LoginStudentSelectPresenterTest { email = "", password = "", login = "", - baseUrl = "", + scrapperBaseUrl = "", + loginMode = Sdk.Mode.SCRAPPER, loginType = Scrapper.LoginType.AUTO, symbols = listOf(symbol), ) From d99c93ec052f48c3ceb95207397212e086b2e2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 7 May 2023 23:40:18 +0200 Subject: [PATCH 183/545] Version 2.0.0 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 22de4b8b6..bcca55873 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 121 - versionName "1.9.2" + versionCode 122 + versionName "2.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:14267a9a" + implementation "io.github.wulkanowy:sdk:2.0.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 b3fec438f..c5ec9de3d 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 1.9.2 +Wersja 2.0.0 -- naprawiliśmy oznaczanie wiadomości jako odczytanych (problem dotyczył głównie kont rodziców z wieloma dziećmi w tej samej szkole) -- naprawiliśmy zapisywanie załączników do wiadomości w sytuacji, gdy ten sam załącznik był dodany do więcej niż jednej wiadomości -- usprawniliśmy ekran z wyborem uczniów i wpisywaniem symbolu przy pierwszym logowaniu +— 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 Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 3fdd47c22196b1f6957c67f957e6dcadaf3826b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 11 May 2023 16:44:10 +0200 Subject: [PATCH 184/545] Fix ime overlaping message content (#2199) --- .../ui/modules/message/send/SendMessageActivity.kt | 9 +++++++-- app/src/main/res/layout/activity_send_message.xml | 6 ++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 28147faed..0ba82f1a0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -142,10 +142,15 @@ class SendMessageActivity : BaseActivity - val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) view.updateLayoutParams { - bottomMargin = bottomInsets.bottom + bottomMargin = if (imeInsets.bottom > navigationBarInsets.bottom) { + imeInsets.bottom + } else { + navigationBarInsets.bottom + } } WindowInsetsCompat.CONSUMED } diff --git a/app/src/main/res/layout/activity_send_message.xml b/app/src/main/res/layout/activity_send_message.xml index fac27d99f..e50cf6b39 100644 --- a/app/src/main/res/layout/activity_send_message.xml +++ b/app/src/main/res/layout/activity_send_message.xml @@ -22,17 +22,15 @@ + android:layout_height="wrap_content"> Date: Thu, 11 May 2023 23:45:20 +0200 Subject: [PATCH 185/545] Add auth dialog (#2198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- app/build.gradle | 2 +- .../wulkanowy/data/db/dao/StudentDao.kt | 4 + .../wulkanowy/data/db/entities/StudentName.kt | 18 +++ .../data/repositories/StudentRepository.kt | 20 +++ .../github/wulkanowy/ui/base/BaseActivity.kt | 5 + .../wulkanowy/ui/base/BaseDialogFragment.kt | 8 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 5 + .../github/wulkanowy/ui/base/BasePresenter.kt | 8 +- .../io/github/wulkanowy/ui/base/BaseView.kt | 2 + .../github/wulkanowy/ui/base/ErrorHandler.kt | 5 + .../wulkanowy/ui/modules/auth/AuthDialog.kt | 81 +++++++++++ .../ui/modules/auth/AuthPresenter.kt | 100 ++++++++++++++ .../wulkanowy/ui/modules/auth/AuthView.kt | 20 +++ .../ui/modules/settings/SettingsFragment.kt | 2 + .../settings/advanced/AdvancedFragment.kt | 5 + .../settings/appearance/AppearanceFragment.kt | 5 + .../notifications/NotificationsFragment.kt | 5 + .../ui/modules/settings/sync/SyncFragment.kt | 5 + .../wulkanowy/utils/StudentExtension.kt | 2 +- app/src/main/res/drawable/ic_auth_success.xml | 10 ++ app/src/main/res/layout/dialog_auth.xml | 128 ++++++++++++++++++ app/src/main/res/values/strings.xml | 11 ++ app/src/main/res/values/styles.xml | 5 + .../ui/modules/settings/ads/AdsFragment.kt | 5 + 24 files changed, 455 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt create mode 100644 app/src/main/res/drawable/ic_auth_success.xml create mode 100644 app/src/main/res/layout/dialog_auth.xml diff --git a/app/build.gradle b/app/build.gradle index bcca55873..16127d9a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -186,7 +186,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:2.0.0" + implementation 'com.github.wulkanowy:sdk:c1573f04c5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index cfa7a72a2..a2f0abac6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.* import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters import javax.inject.Singleton @@ -19,6 +20,9 @@ abstract class StudentDao { @Update(entity = Student::class) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) + @Update(entity = Student::class) + abstract suspend fun update(studentName: StudentName) + @Query("SELECT * FROM Students WHERE is_current = 1") abstract suspend fun loadCurrent(): Student? diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt new file mode 100644 index 000000000..46f754b5e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentName.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity +data class StudentName( + + @ColumnInfo(name = "student_name") + val studentName: String + +) : Serializable { + + @PrimaryKey + var id: Long = 0 +} 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 4c7069ef6..a6bb72433 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 @@ -6,7 +6,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.exceptions.NoCurrentStudentException @@ -14,6 +16,7 @@ import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.security.decrypt import io.github.wulkanowy.utils.security.encrypt import kotlinx.coroutines.withContext @@ -146,4 +149,21 @@ class StudentRepository @Inject constructor( suspend fun isOneUniqueStudent() = getSavedStudents(false) .distinctBy { it.student.studentName }.size == 1 + + suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = + sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .authorizePermission(pesel) + + suspend fun refreshStudentName(student: Student, semester: Semester) { + val newCurrentApiStudent = sdk.init(student) + .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .getCurrentStudent() ?: return + + val studentName = StudentName( + studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}" + ).apply { id = student.id } + + studentDb.update(studentName) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 7914df81c..f622209a7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -10,6 +10,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor @@ -76,6 +77,10 @@ abstract class BaseActivity, VB : ViewBinding> : .show() } + override fun showAuthDialog() { + AuthDialog.newInstance().show(supportFragmentManager, "auth_dialog") + } + override fun showChangePasswordSnackbar(redirectUrl: String) { messageContainer?.let { Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 561d181a9..84540b1ca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -5,10 +5,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding import com.google.android.material.elevation.SurfaceColors +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.lifecycleAwareVariable import javax.inject.Inject @@ -40,17 +40,19 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView (activity as? BaseActivity<*, *>)?.showChangePasswordSnackbar(redirectUrl) } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } - @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) view.setBackgroundColor(SurfaceColors.SURFACE_3.getColor(requireContext())) } - @CallSuper override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index dbc5af3a9..b25346a7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -7,6 +7,7 @@ import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.utils.lifecycleAwareVariable abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), @@ -42,6 +43,10 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme (activity as? BaseActivity<*, *>)?.showExpiredDialog() } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun openClearLoginView() { (activity as? BaseActivity<*, *>)?.openClearLoginView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 15c069f54..2d913103b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,10 +1,15 @@ package io.github.wulkanowy.ui.base import io.github.wulkanowy.data.repositories.StudentRepository -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.launch import timber.log.Timber open class BasePresenter( @@ -26,6 +31,7 @@ open class BasePresenter( onSessionExpired = view::showExpiredDialog onNoCurrentStudent = view::openClearLoginView onPasswordChangeRequired = view::showChangePasswordSnackbar + onAuthorizationRequired = view::showAuthDialog } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index d3165ea44..b31737e2b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -8,6 +8,8 @@ interface BaseView { fun showExpiredDialog() + fun showAuthDialog() + fun openClearLoginView() fun showErrorDetailsDialog(error: Throwable) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index afe200e9a..0a41a47b3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.base import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException +import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.utils.getErrorString @@ -20,6 +21,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co var onPasswordChangeRequired: (String) -> Unit = {} + var onAuthorizationRequired: () -> Unit = {} + fun dispatch(error: Throwable) { Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) @@ -31,6 +34,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) is ScramblerException, is BadCredentialsException -> onSessionExpired() is NoCurrentStudentException -> onNoCurrentStudent() + is AuthorizationRequiredException -> onAuthorizationRequired() } } @@ -39,5 +43,6 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co onSessionExpired = {} onNoCurrentStudent = {} onPasswordChangeRequired = {} + onAuthorizationRequired = {} } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt new file mode 100644 index 000000000..fa29df473 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt @@ -0,0 +1,81 @@ +package io.github.wulkanowy.ui.modules.auth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogAuthBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import javax.inject.Inject + +@AndroidEntryPoint +class AuthDialog : BaseDialogFragment(), AuthView { + + @Inject + lateinit var presenter: AuthPresenter + + companion object { + fun newInstance() = AuthDialog() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.FullScreenDialogStyle) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return DialogAuthBinding.inflate(inflater).apply { binding = this }.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + presenter.onAttachView(this) + + binding.authInput.doOnTextChanged { text, _, _, _ -> + presenter.onPeselChange(text?.toString()) + } + + binding.authButton.setOnClickListener { presenter.authorize() } + binding.authSuccessButton.setOnClickListener { + activity?.recreate() + dismiss() + } + binding.authButtonSkip.setOnClickListener { dismiss() } + } + + override fun enableAuthButton(isEnabled: Boolean) { + binding.authButton.isEnabled = isEnabled + } + + override fun showProgress(show: Boolean) { + binding.authProgress.isVisible = show + } + + override fun showPeselError(show: Boolean) { + binding.authInputLayout.error = getString(R.string.auth_api_error).takeIf { show } + } + + override fun showInvalidPeselError(show: Boolean) { + binding.authInputLayout.error = getString(R.string.auth_invalid_error).takeIf { show } + } + + override fun showSuccess(show: Boolean) { + binding.authSuccess.isVisible = show + } + + override fun showContent(show: Boolean) { + binding.authForm.isVisible = show + } + + override fun showDescriptionWithName(name: String) { + binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt new file mode 100644 index 000000000..8f579712b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt @@ -0,0 +1,100 @@ +package io.github.wulkanowy.ui.modules.auth + +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import kotlinx.coroutines.launch +import javax.inject.Inject + +class AuthPresenter @Inject constructor( + private val semesterRepository: SemesterRepository, + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + private var pesel: String = "" + + override fun onAttachView(view: AuthView) { + super.onAttachView(view) + view.enableAuthButton(pesel.length == 11) + view.showSuccess(false) + view.showProgress(false) + + loadName() + } + + private fun loadName() { + presenterScope.launch { + runCatching { studentRepository.getCurrentStudent(false) } + .onSuccess { view?.showDescriptionWithName(it.studentName) } + .onFailure { errorHandler.dispatch(it) } + } + } + + fun onPeselChange(newPesel: String?) { + pesel = newPesel.orEmpty() + + view?.enableAuthButton(pesel.length == 11) + view?.showPeselError(false) + view?.showInvalidPeselError(false) + } + + fun authorize() { + presenterScope.launch { + view?.showProgress(true) + view?.showContent(false) + + if (!isValidPESEL(pesel)) { + view?.showInvalidPeselError(true) + view?.showProgress(false) + view?.showContent(true) + return@launch + } + + runCatching { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + + val isSuccess = studentRepository.authorizePermission(student, semester, pesel) + if (isSuccess) { + studentRepository.refreshStudentName(student, semester) + } + isSuccess + } + .onFailure { errorHandler.dispatch(it) } + .onSuccess { + if (it) { + view?.showSuccess(true) + view?.showContent(false) + view?.showPeselError(false) + } else { + view?.showSuccess(false) + view?.showContent(true) + view?.showPeselError(true) + } + } + + view?.showProgress(false) + } + } + + private fun isValidPESEL(peselString: String): Boolean { + if (peselString.length != 11) { + return false + } + + val weights = intArrayOf(1, 3, 7, 9, 1, 3, 7, 9, 1, 3) + var sum = 0 + + for (i in 0 until 10) { + sum += weights[i] * Character.getNumericValue(peselString[i]) + } + + sum %= 10 + sum = 10 - sum + sum %= 10 + + return sum == Character.getNumericValue(peselString[10]) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt new file mode 100644 index 000000000..d7e1917c2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthView.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.ui.modules.auth + +import io.github.wulkanowy.ui.base.BaseView + +interface AuthView : BaseView { + + fun enableAuthButton(isEnabled: Boolean) + + fun showProgress(show: Boolean) + + fun showPeselError(show: Boolean) + + fun showInvalidPeselError(show: Boolean) + + fun showSuccess(show: Boolean) + + fun showContent(show: Boolean) + + fun showDescriptionWithName(name: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index d56cdfa7c..21f564988 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -31,4 +31,6 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin override fun showErrorDetailsDialog(error: Throwable) {} override fun showChangePasswordSnackbar(redirectUrl: String) {} + + override fun showAuthDialog() {} } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index b4ba5bc4b..41e9e8b1a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject @@ -62,6 +63,10 @@ class AdvancedFragment : PreferenceFragmentCompat(), ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun onResume() { super.onResume() preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 1f6d5143b..493ab5d7f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -9,6 +9,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject @@ -78,6 +79,10 @@ class AppearanceFragment : PreferenceFragmentCompat(), ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun onResume() { super.onResume() preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 98ac15739..35c1faa45 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -21,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openInternetBrowser @@ -148,6 +149,10 @@ class NotificationsFragment : PreferenceFragmentCompat(), ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun showFixSyncDialog() { MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.pref_notify_fix_sync_issues) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 2a804d9f6..df2e1348a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -10,6 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import javax.inject.Inject @@ -99,6 +100,10 @@ class SyncFragment : PreferenceFragmentCompat(), ErrorDialog.newInstance(error).show(childFragmentManager, "error_details") } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun onResume() { super.onResume() preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) diff --git a/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt index fdd0610a0..132a3085e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt @@ -2,4 +2,4 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.data.db.entities.Student -inline val Student.nickOrName get() = if (nick.isBlank()) studentName else nick +inline val Student.nickOrName get() = nick.ifBlank { studentName } diff --git a/app/src/main/res/drawable/ic_auth_success.xml b/app/src/main/res/drawable/ic_auth_success.xml new file mode 100644 index 000000000..015553c02 --- /dev/null +++ b/app/src/main/res/drawable/ic_auth_success.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_auth.xml b/app/src/main/res/layout/dialog_auth.xml new file mode 100644 index 000000000..e2e2aa304 --- /dev/null +++ b/app/src/main/res/layout/dialog_auth.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e53fd2f1..1eff95698 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -808,6 +808,17 @@ 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 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b0bf28194..9d1f07458 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -78,4 +78,9 @@ @drawable/background_material_alert_dialog @layout/m3_alert_dialog + + diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index de4c591e1..af6a83404 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -13,6 +13,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogAdsConsentBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject @@ -146,6 +147,10 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { (activity as? BaseActivity<*, *>)?.openClearLoginView() } + override fun showAuthDialog() { + AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + } + override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } From 5a2622871f9e5a61b419e100dd546457a36c63bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 12 May 2023 00:21:24 +0200 Subject: [PATCH 186/545] Version 2.0.1 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 16127d9a3..48ca28a0e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 122 - versionName "2.0.0" + versionCode 123 + versionName "2.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,7 +161,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS - userFraction = 0.50d + userFraction = 0.10d updatePriority = 2 enabled.set(false) } @@ -186,7 +186,7 @@ ext { } dependencies { - implementation 'com.github.wulkanowy:sdk:c1573f04c5' + implementation 'io.github.wulkanowy:sdk:2.0.1' 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 c5ec9de3d..d079f986c 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -4,5 +4,6 @@ Wersja 2.0.0 — 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 Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From cbef160adaead03b862035c8cfc3b6dab9355716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 12 May 2023 00:41:53 +0200 Subject: [PATCH 187/545] New Crowdin updates (#2193) --- app/src/main/res/values-cs/strings.xml | 9 +++++++++ app/src/main/res/values-da-rDK/strings.xml | 9 +++++++++ app/src/main/res/values-de/strings.xml | 9 +++++++++ app/src/main/res/values-es-rES/strings.xml | 9 +++++++++ app/src/main/res/values-pl/strings.xml | 9 +++++++++ app/src/main/res/values-ru/strings.xml | 11 ++++++++++- app/src/main/res/values-sk/strings.xml | 9 +++++++++ app/src/main/res/values-uk/strings.xml | 9 +++++++++ 8 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index bedb491bc..beb2996be 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -809,6 +809,15 @@ Restartování aplikace 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 + 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 Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index ebec11b25..b9de57f77 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -721,6 +721,15 @@ 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 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1e1785bff..4fdd71b50 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -721,6 +721,15 @@ 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 Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index ebec11b25..b9de57f77 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -721,6 +721,15 @@ 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 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f797a4dcb..207b12e98 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -809,6 +809,15 @@ Ponowne uruchomienie aplikacji W celu zapisania zmian aplikacja musi zostać ponownie uruchomiona Uruchom ponownie + + Autoryzacja została odrzucona. Podano dane niezgodne z danymi w sekretariacie. + Nieprawidłowy PESEL + PESEL + Potwierdź + Autoryzacja zakończona pomyślnie + Autoryzacja + Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej + Na razie pomiń Brak połączenia z internetem Wystąpił błąd. Sprawdź poprawność daty w urządzeniu diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7a42e3880..726e95465 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -778,7 +778,7 @@ Значения плюса и минуса, расчёт средней оценки Расширенные Версия приложения, разработчики, соц. сети - Посмотреть рекламу, чтобы поддержать преокт + Displaying advertisements, project support Новые оценки Новое домашнее задание @@ -809,6 +809,15 @@ Перезапуск приложение Для сохранения изменений необходимо перезапустить приложение Перезапустить + + 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 Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index cde3178bb..7e42c6cd4 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -809,6 +809,15 @@ Reštartovanie aplikácie 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 + 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 Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9602aabba..66cd23870 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -809,6 +809,15 @@ Перезавантаження додатку Додаток потрібно перезавантажити для збереження змін Перезавантажити + + 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 Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою From b8ac72c247a70ae4a68349a3239d3ff280bd0335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 12 May 2023 00:45:48 +0200 Subject: [PATCH 188/545] Version 2.0.2 --- app/build.gradle | 4 ++-- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 48ca28a0e..de0c2a3ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 123 - versionName "2.0.1" + versionCode 124 + versionName "2.0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" 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 d079f986c..378dedceb 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.0 +Wersja 2.0.2 — 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 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 189/545] 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 de0c2a3ce..0f6789e8e 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 190/545] 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 0f6789e8e..85ac4833f 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 191/545] 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 3c8552d20..c63a8fd0c 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 192/545] 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 85ac4833f..4ea086a19 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 193/545] 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 4ea086a19..3a6744c14 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 194/545] 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 2c06e45fd..4ad4e9d67 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 7d2f0cfed..5d9aa22a6 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 cffb284ec..bb47b22ed 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 8986f3576..aa58fa09e 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 ac1b1c196..87057c61d 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 9d1f07458..603e22abc 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 195/545] 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 beb2996be..f8c19dff0 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 4fdd71b50..500553e27 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 7e42c6cd4..950eb01e0 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 196/545] 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 3a6744c14..a4f230b88 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 378dedceb..aee30290a 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 197/545] 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 1b1c1d39b..a40b5dcee 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 198/545] 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 c63a8fd0c..7161e4c31 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 199/545] 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 3ce618ca7..e8a220ddf 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 20082590d..f2e9f016f 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 7f8591bb1..bc4b36470 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 a4f230b88..ad83461a3 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 7161e4c31..88079e54d 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 38603830b..5a8099a12 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 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e8be595e3..6ec1567a0 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 65dcd68d6..79a61d421 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 200/545] 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 ad83461a3..f500e7795 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 88079e54d..eac46b05c 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 201/545] 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 f500e7795..aaa27b9c7 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 202/545] 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 aaa27b9c7..2cde7cdaa 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 203/545] 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 2cde7cdaa..3b5fadb13 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 204/545] 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 3b5fadb13..83be53fc5 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 205/545] 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 83be53fc5..729488737 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 aee30290a..858b44d42 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 206/545] 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 fd9482613..ac7d1b029 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 207/545] 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 729488737..b1116c588 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 858b44d42..720952730 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 208/545] 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 ac7d1b029..0fd49f6ac 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 5a8099a12..4c54d414a 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 209/545] 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 b1116c588..5cebcb31b 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 720952730..df69a331d 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 210/545] 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 5cebcb31b..40ea252c1 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 211/545] 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 40ea252c1..3b7ee510d 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 212/545] 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 eac46b05c..1a70f82af 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 213/545] 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 3b7ee510d..e72beed28 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 214/545] 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 1a70f82af..b7ac37edc 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 215/545] 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 e72beed28..251aa4b6e 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 216/545] 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 251aa4b6e..05070d102 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 217/545] 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 b7ac37edc..69c0905af 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 218/545] 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 05070d102..929a9db99 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 219/545] 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 401aef313..8297bc142 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 220/545] 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 726e95465..9029f4b91 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 66cd23870..be136ea29 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 221/545] 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 929a9db99..ec62e19f1 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 df69a331d..066485d3d 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 222/545] 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 ec62e19f1..19fbf6dec 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 000000000..1a26e717b --- /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 0aea86dab..882a70167 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 76da9643d..e1116733c 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 bcf26a5e1..72c4861c9 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 a6bb72433..42d1eb84e 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 ae6c22492..2c11bb6d5 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 ead2d71af..13d2c14a7 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 ab56bd786..a17ad0035 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 34062d938..afd33e3b6 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 43ba3fe1d..1085ff509 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 ed70eb128..85f428415 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 e5c680d6f..5fb260620 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 7e1fe3b21..70862e828 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 03ea95fa6..91fe1ac3c 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 481cad113..889d64ea1 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 43016db4c..37551decb 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 1eff95698..98c316cb0 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 c8d95829e..eac1389f4 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 1d6dfaff0..5a1877cc0 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 f58a5381d..34a8fe991 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 b94002c0d..31ea3322b 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 eb1f53006..a6440f72a 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 da292c51c..06aabec72 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 223/545] 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 f8c19dff0..964329dae 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 b9de57f77..5c7d02a0f 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 500553e27..96423e35e 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 b9de57f77..5c7d02a0f 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 207b12e98..2c7077977 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 9029f4b91..eb8be0028 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 950eb01e0..bcbd832ae 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 be136ea29..30a587cc4 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 224/545] 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 19fbf6dec..000cc09ca 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 066485d3d..e881cfdaf 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 932093676..522b6e116 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 225/545] 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 000cc09ca..2231da52b 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 226/545] 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 69c0905af..c5a48ec9d 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 227/545] 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 c5a48ec9d..757910b1c 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 228/545] 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 757910b1c..150bd4cfb 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 229/545] 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 2231da52b..46f709c85 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 230/545] 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 150bd4cfb..62f6389f0 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 231/545] 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 46f709c85..4589331fc 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 232/545] 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 62f6389f0..d12932db3 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 233/545] 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 4589331fc..54cc97ffd 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 234/545] 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 d12932db3..75191aade 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 235/545] 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 54cc97ffd..fdb8d20f7 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 236/545] 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 fdb8d20f7..9e0743715 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 237/545] 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 9e0743715..f3ea03c1e 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 238/545] 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 f3ea03c1e..596393f54 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 87f4812da..2b4cb5975 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 75191aade..6a378d481 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 239/545] 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 a69aaa955..af9bb737a 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 240/545] 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 6a378d481..8bdcdedfa 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 241/545] 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 8bdcdedfa..dba021521 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 242/545] 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 596393f54..f8603cc88 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 10724868a..543c9540f 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 18249ba8b..18ff93392 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 f614c8ca9..54c73e209 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 b0c03fb11..9ba36876e 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 243/545] 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 dba021521..9584caaca 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 244/545] 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 9c5abe1c2..d545413da 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 624ca30f4..cc48539a5 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 7f64c4ebe..48298d675 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 096357584..510c70c0c 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 2f91489a9..09c50b4f1 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 27c9db66c..01f4525ec 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 3cdad0c81..555d8cb10 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 245/545] 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 000000000..ac2b6e9e5 --- /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 000000000..5c7d02a0f --- /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 30a587cc4..f89e38fcd 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 246/545] 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 53d9beadd..c8fccb23d 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 348a4054b..1b4893401 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 4317fb7fa..02bc13a16 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 cf6bad19e..37a2d422a 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 def4a2751..7fdc6e181 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 6c54d9fcb..3ed685cd7 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 56f23b6fa..cd7b72843 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 c5a947939..7f5f140b2 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 592cbd604..4364e8681 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 ec92e9c20..90f93b145 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 6ece6621b..247af4342 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 6c81100d9..fefd9b13c 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 716639c0f..e7fa542a3 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 98c316cb0..9dc7e7969 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 95f6f383a..8185de81c 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 247/545] 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 f8603cc88..e188d709f 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 248/545] 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 e188d709f..d43b667cd 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 249/545] 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 d43b667cd..74ef02bc2 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 9584caaca..c5a6f5984 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 8707e8b50..9b0a13f0f 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 250/545] 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 74ef02bc2..197ea3569 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 251/545] 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 964329dae..ff461d420 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 5c7d02a0f..259a42647 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 96423e35e..1836d0479 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 5c7d02a0f..259a42647 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 5c7d02a0f..259a42647 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 2c7077977..0c1bbf785 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 eb8be0028..606971740 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 bcbd832ae..20d8818b3 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 f89e38fcd..db5c3cb0d 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 252/545] 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 197ea3569..8ff342070 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 253/545] 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 8ff342070..5f148144a 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 254/545] 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 c5a6f5984..2b52c068b 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 255/545] 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 5f148144a..824c8376d 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 256/545] 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 af9bb737a..16731297d 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 257/545] 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 824c8376d..37b165c17 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 258/545] 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 b2849931a..a7629c22f 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 000000000..c8310c02d --- /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 1b4893401..85c74072e 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 d48556fa6..ffdb07ecd 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 d917e7d51..1201937c0 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 92716ace8..105ece38c 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 d06874082..0f8395ded 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 d545413da..4e0578e2b 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 000000000..166b1a8fb --- /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 000000000..12fddb752 --- /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 000000000..a48b3645e --- /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 456005747..8872b7ab1 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 0c1bbf785..a2b5510ec 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 fefd9b13c..8d69f25c8 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 e7fa542a3..c48381e8c 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 312f0b878..f56707c89 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 9dc7e7969..ce277bdce 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 62216c760..7177d396c 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 259/545] 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 37b165c17..136c54303 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 92bb37081..dd44df70f 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 380d6bf6e..e3b8a3b4c 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 d8256869f..31098d2ef 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 b7d3ecc94..e8ba8a876 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 260/545] 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 23073adf1..2cf402631 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 ff461d420..a63a0aa12 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 ac2b6e9e5..5aff12dec 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 259a42647..2abf1a4a6 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 d9cac1959..d1001c74b 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 1836d0479..f08a504a5 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 ac2b6e9e5..5aff12dec 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 259a42647..2abf1a4a6 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 ac2b6e9e5..5aff12dec 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 259a42647..2abf1a4a6 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 8a8c260da..df3629c02 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 606971740..d075dac6c 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 e4331315a..fd393394f 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 20d8818b3..d1990e35e 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 44acd18e9..55cf905bb 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 db5c3cb0d..876562d31 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 261/545] 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 136c54303..d0ae8894c 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 000000000..2eff12231 --- /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 6b611e477..2c5bf0ea9 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 882a70167..48a2942c9 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 9d3beae1f..7bc8d12a2 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 97fec69b7..875c2a3a5 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 000000000..2fc8718f9 --- /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 000000000..531684e4e --- /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 c9655b722..b831ee755 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 000000000..b55bf899d --- /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 9c15acc35..f033b5947 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 ac2c896dc..ecf084c68 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 4ad4e9d67..7c74cae80 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 1085ff509..ff7fd864b 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 85f428415..4e0404d9b 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 5fb260620..5b4dcadfe 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 39f3146c8..fc5e5f35e 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 262/545] 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 d0ae8894c..b09078a9e 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 e881cfdaf..aa934ce98 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 From 10f9812495f83ec7be0ffcea927b9eb95bdd9adc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:04:34 +0000 Subject: [PATCH 263/545] Bump com.android.tools.build:gradle from 8.1.0 to 8.1.1 (#2286) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2b52c068b..7adbb4694 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.1.0' + classpath 'com.android.tools.build:gradle:8.1.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.1.300' From 5238e4d187ec314fc180630e0ba1ad687065a7da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:04:57 +0000 Subject: [PATCH 264/545] Bump com.google.android.gms:play-services-ads from 22.2.0 to 22.3.0 (#2285) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b09078a9e..7d8cc08ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -254,7 +254,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.2.0' + playImplementation 'com.google.android.gms:play-services-ads:22.3.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.303' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.300' From aff56a83118e33259039d66c6d89bbfb4e2796f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:05:12 +0000 Subject: [PATCH 265/545] Bump com.google.firebase:firebase-bom from 32.2.2 to 32.2.3 (#2284) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7d8cc08ba..03c6c837c 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.2') + playImplementation platform('com.google.firebase:firebase-bom:32.2.3') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From a77b3d4cd73961f0b17c01dd3866f95fc4b176b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:05:29 +0000 Subject: [PATCH 266/545] Bump com.google.firebase:firebase-crashlytics-gradle from 2.9.8 to 2.9.9 (#2283) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7adbb4694..3f4d6d73b 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.8' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' 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 50a177d18c01f1ecbdc04169034a0e49780cae9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:05:47 +0000 Subject: [PATCH 267/545] Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2282) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 03c6c837c..b578b4cdd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "androidx.core:core-ktx:1.10.1" From c82e6ae95bb25845a15fc067a5858a43c3b39f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 1 Sep 2023 19:06:48 +0200 Subject: [PATCH 268/545] New Crowdin updates (#2287) --- app/src/main/res/values-uk/preferences_values.xml | 6 +++--- app/src/main/res/values-uk/strings.xml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index 55cf905bb..c02efb54a 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -52,9 +52,9 @@ Середня оцінка з цілого року - 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 876562d31..d01dcfbaf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -186,10 +186,10 @@ Зміна вчителя з %1$s на %2$s Зміна теми з %1$s на %2$s - No lesson - No lessons - No lessons - No lessons + Немає уроку + Немає уроків + Немає уроків + Немає уроків Зміна у розкладі @@ -706,7 +706,7 @@ Розгортання оцінок Позначити поточний урок Показувати групи поруч з темами - Show empty tiles where there\'s no lesson + Показувати порожні плитки там, де немає уроків Показувати діаграми в оцінках класу Показати предмети без оцінок Схема кольорів оцінок From 8cc69728aac101c6821e08ef062c0470fcbc40f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:29:02 +0000 Subject: [PATCH 269/545] Bump kotlin_version from 1.9.0 to 1.9.10 (#2281) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3f4d6d73b..ba792ef1e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.9.0' + kotlin_version = '1.9.10' about_libraries = '10.8.3' hilt_version = "2.47" } @@ -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.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.11" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.13" classpath 'com.android.tools.build:gradle:8.1.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.15' From 8478b8b7ed08043a2f75e7d298f6e2f6dcfa6b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:13:24 +0000 Subject: [PATCH 270/545] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2291) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ba792ef1e..b1e35375f 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' 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 "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.3.1.3277" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From 017d46e5dbb3e828074d22ab311787d390fc98ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:13:39 +0000 Subject: [PATCH 271/545] Bump hilt_version from 2.47 to 2.48 (#2290) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b1e35375f..51f4be8f7 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.10' about_libraries = '10.8.3' - hilt_version = "2.47" + hilt_version = "2.48" } repositories { mavenCentral() From 0a402378099d02629187a04cb6761a16bfb56ee9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:30:02 +0000 Subject: [PATCH 272/545] Bump com.github.bastienpaulfr:Treessence from 1.0.5 to 1.1.2 (#2289) --- app/build.gradle | 2 +- app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt | 4 ++-- app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt | 2 +- app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt | 2 +- .../play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b578b4cdd..fb03ca162 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -239,7 +239,7 @@ dependencies { implementation "com.jakewharton.timber:timber:5.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1" - implementation 'com.github.bastienpaulfr:Treessence:1.0.5' + implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "io.coil-kt:coil:2.4.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.1" diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt index 377e83666..2b1f1d30e 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.utils import android.util.Log import com.huawei.agconnect.crash.AGConnectCrash -import fr.bipi.tressence.base.FormatterPriorityTree -import fr.bipi.tressence.common.StackTraceRecorder +import fr.bipi.treessence.base.FormatterPriorityTree +import fr.bipi.treessence.common.StackTraceRecorder class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index a39a38745..dc1061018 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -6,7 +6,7 @@ import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration import com.yariksoffice.lingver.Lingver import dagger.hilt.android.HiltAndroidApp -import fr.bipi.tressence.file.FileLoggerTree +import fr.bipi.treessence.file.FileLoggerTree import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.utils.* diff --git a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt index 1e9f49a66..00ed2c110 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.kt @@ -7,7 +7,7 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import fr.bipi.tressence.common.filters.Filter +import fr.bipi.treessence.common.filters.Filter import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import timber.log.Timber diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index f980bc4bb..2e5fad622 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.utils import android.util.Log import com.google.firebase.crashlytics.FirebaseCrashlytics -import fr.bipi.tressence.base.FormatterPriorityTree -import fr.bipi.tressence.common.StackTraceRecorder +import fr.bipi.treessence.base.FormatterPriorityTree +import fr.bipi.treessence.common.StackTraceRecorder class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { From db02f0c1e12d815a1d0f619ebe99816a18654811 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:32:56 +0000 Subject: [PATCH 273/545] Bump com.google.android.gms:play-services-ads from 22.3.0 to 22.4.0 (#2301) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index fb03ca162..d1fbfd16a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -254,7 +254,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.3.0' + playImplementation 'com.google.android.gms:play-services-ads:22.4.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.303' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.300' From 05a804832bd993f3afc5b588ba1eb292548c1a82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:33:10 +0000 Subject: [PATCH 274/545] Bump com.google.gms:google-services from 4.3.15 to 4.4.0 (#2300) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 51f4be8f7..8aec83fbb 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.13" classpath 'com.android.tools.build:gradle:8.1.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.15' + classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.300' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" From 81d8f7ea4826441f9d5b632d83889fadf949a6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:33:30 +0000 Subject: [PATCH 275/545] Bump androidx.lifecycle:lifecycle-livedata-ktx from 2.6.1 to 2.6.2 (#2295) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d1fbfd16a..46c6ed719 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -219,7 +219,7 @@ dependencies { implementation "androidx.work:work-runtime-ktx:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" From 09d16cf6d8b5f6016fdeadf6da3ec26b632d518a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:33:50 +0000 Subject: [PATCH 276/545] Bump androidx.annotation:annotation from 1.6.0 to 1.7.0 (#2293) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 46c6ed719..5b4eeacd1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -203,7 +203,7 @@ dependencies { implementation "androidx.activity:activity-ktx:1.7.2" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.6.1" - implementation "androidx.annotation:annotation:1.6.0" + implementation "androidx.annotation:annotation:1.7.0" implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.recyclerview:recyclerview:1.3.1" From aabd7345c1521066666f376fdc0c5c0e9b1a65ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:49:25 +0000 Subject: [PATCH 277/545] Bump com.google.firebase:firebase-bom from 32.2.3 to 32.3.1 (#2299) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5b4eeacd1..6def2c133 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.3') + playImplementation platform('com.google.firebase:firebase-bom:32.3.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 1d8d71709ff84c04bb36ae43d52bd186aebcaf7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:49:46 +0000 Subject: [PATCH 278/545] Bump com.huawei.agconnect:agconnect-crash from 1.9.1.300 to 1.9.1.301 (#2298) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6def2c133..57755d743 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -257,7 +257,7 @@ dependencies { playImplementation 'com.google.android.gms:play-services-ads:22.4.0' hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.303' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From ff2aa6f195990be56bdc87a115075a5ef57e7bb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:59:38 +0000 Subject: [PATCH 279/545] Bump com.huawei.agconnect:agcp from 1.9.1.300 to 1.9.1.301 (#2297) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8aec83fbb..e7543d972 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.1.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' - classpath 'com.huawei.agconnect:agcp:1.9.1.300' + classpath 'com.huawei.agconnect:agcp:1.9.1.301' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" From c4a3da93ca41cb78806ddfd6aff1ab42e72739c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:00:06 +0000 Subject: [PATCH 280/545] Bump com.huawei.hms:hianalytics from 6.10.0.303 to 6.12.0.300 (#2294) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 57755d743..191a574e9 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.4.0' - hmsImplementation 'com.huawei.hms:hianalytics:6.10.0.303' + hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From afd0c8513a034a89bb240f0592d442793b1edb7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:43:16 +0000 Subject: [PATCH 281/545] Bump mockk from 1.13.7 to 1.13.8 (#2305) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 191a574e9..abe1a4b18 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.7" + mockk = "1.13.8" coroutines = "1.7.3" } From 646b4a149d64b55ac9699d62332831ad65c8a32e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:43:41 +0000 Subject: [PATCH 282/545] Bump about_libraries from 10.8.3 to 10.9.0 (#2304) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e7543d972..651e9ca16 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.10' - about_libraries = '10.8.3' + about_libraries = '10.9.0' hilt_version = "2.48" } repositories { From 0fa197d5204a5a56b51da2a497dd5241bc60c5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 25 Sep 2023 19:43:57 +0200 Subject: [PATCH 283/545] New Crowdin updates (#2303) --- app/src/main/res/values-cs/preferences_values.xml | 6 +++--- app/src/main/res/values-cs/strings.xml | 10 +++++----- app/src/main/res/values-sk/preferences_values.xml | 6 +++--- app/src/main/res/values-sk/strings.xml | 10 +++++----- app/src/main/res/values-uk/strings.xml | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index 2cf402631..1590c47ab 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -52,9 +52,9 @@ Průměr známek z celého roku - Don\'t show - Only between lessons - Before and between lessons + Nezobrazovat + Pouze mezi lekcemi + Před a mezi lekcemi Šťastné číslo diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a63a0aa12..e9e049326 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -186,10 +186,10 @@ 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 + Žádné lekce + Žádné lekce + Žádné lekce + Žádné lekce Změna plánu lekcí @@ -706,7 +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 prázdné dlaždice, kde není žádná lekce 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-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml index fd393394f..6cd221540 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -52,9 +52,9 @@ Priemer známok z celého roka - Don\'t show - Only between lessons - Before and between lessons + Nezobrazovať + Iba medzi lekciami + Pred a medzi lekciami Šťastné číslo diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d1990e35e..d059927eb 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -186,10 +186,10 @@ 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 + Žiadne lekcie + Žiadne lekcie + Žiadne lekcie + Žiadne lekcie Zmena plánu lekcií @@ -706,7 +706,7 @@ Rozvijanie známok Označiť aktuálne lekciu Zobraziť skupiny vedľa predmetov - Show empty tiles where there\'s no lesson + Zobraziť prázdne dlaždice, kde nie je žiadne lekcie 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/strings.xml b/app/src/main/res/values-uk/strings.xml index d01dcfbaf..b46b9afe9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -55,7 +55,7 @@ Використовуйте призначений логін замість адреси e-mail Використовуйте призначений логін або адресу e-mail в @%1$s Неправильний symbol - Студента не знайдено. Перевірте symbol та обранний тип щоденника UONET+ + Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+ Цього учня вже авторизовано Symbol можно знайти на сторінці щоденника у Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nПереконайтеся, що ви вказали відповідний щоденник у полі Тип щоденника UONET+ на першому екрані логування Виберіть учнів для авторизації в додатку From 95b4d53fac498f81ef82d74b0836763fbd43c9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 25 Sep 2023 19:44:13 +0200 Subject: [PATCH 284/545] Add schools API integration (#2302) --- app/build.gradle | 3 + .../github/wulkanowy/utils/IntegrityHelper.kt | 11 +++ .../github/wulkanowy/utils/IntegrityHelper.kt | 11 +++ .../io/github/wulkanowy/data/DataModule.kt | 17 ++++- .../wulkanowy/data/api/SchoolsService.kt | 14 ++++ .../github/wulkanowy/data/pojos/LoginEvent.kt | 21 ++++++ .../data/repositories/SchoolsRepository.kt | 68 +++++++++++++++++++ .../LoginStudentSelectPresenter.kt | 29 +++++--- .../java/io/github/wulkanowy/utils/AppInfo.kt | 3 +- .../github/wulkanowy/utils/IntegrityHelper.kt | 27 ++++++++ .../LoginStudentSelectPresenterTest.kt | 6 ++ 11 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt create mode 100644 app/src/play/java/io/github/wulkanowy/utils/IntegrityHelper.kt diff --git a/app/build.gradle b/app/build.gradle index abe1a4b18..d85d0a3d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" + buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' } debug { minifyEnabled false @@ -78,6 +79,7 @@ android { versionNameSuffix "-dev" ext.enableCrashlytics = project.hasProperty("enableFirebase") buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" + buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' } } @@ -255,6 +257,7 @@ dependencies { 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.4.0' + playImplementation "com.google.android.play:integrity:1.2.0" hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt new file mode 100644 index 000000000..7af68058c --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/IntegrityHelper.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class IntegrityHelper @Inject constructor() { + + @Suppress("UNUSED_PARAMETER") + fun getIntegrityToken(requestId: String): String? = null +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt new file mode 100644 index 000000000..7af68058c --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/IntegrityHelper.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.utils + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class IntegrityHelper @Inject constructor() { + + @Suppress("UNUSED_PARAMETER") + fun getIntegrityToken(requestId: String): String? = null +} diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index c9e4990f9..bea3f7064 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -14,6 +14,7 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import io.github.wulkanowy.data.api.AdminMessageService +import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -82,19 +83,29 @@ internal class DataModule { @Singleton @Provides - fun provideRetrofit( + fun provideAdminMessageService( okHttpClient: OkHttpClient, json: Json, appInfo: AppInfo - ): Retrofit = Retrofit.Builder() + ): AdminMessageService = Retrofit.Builder() .baseUrl(appInfo.messagesBaseUrl) .client(okHttpClient) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() + .create() @Singleton @Provides - fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create() + fun provideSchoolsService( + okHttpClient: OkHttpClient, + json: Json, + appInfo: AppInfo, + ): SchoolsService = Retrofit.Builder() + .baseUrl(appInfo.schoolsBaseUrl) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + .create() @Singleton @Provides diff --git a/app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt b/app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt new file mode 100644 index 000000000..a7da9b63c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/api/SchoolsService.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.api + +import io.github.wulkanowy.data.pojos.IntegrityRequest +import io.github.wulkanowy.data.pojos.LoginEvent +import retrofit2.http.Body +import retrofit2.http.POST +import javax.inject.Singleton + +@Singleton +interface SchoolsService { + + @POST("/log/loginEvent") + suspend fun logLoginEvent(@Body request: IntegrityRequest) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt new file mode 100644 index 000000000..c2b4d2ded --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/LoginEvent.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.pojos + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginEvent( + val uuid: String, + val schoolName: String, + val schoolShort: String, + val schoolAddress: String, + val scraperBaseUrl: String, + val symbol: String, + val schoolId: String, + val loginType: String, +) + +@Serializable +data class IntegrityRequest( + val tokenString: String, + val data: T, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt new file mode 100644 index 000000000..9c6429343 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.api.SchoolsService +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.pojos.IntegrityRequest +import io.github.wulkanowy.data.pojos.LoginEvent +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.utils.IntegrityHelper +import io.github.wulkanowy.utils.getCurrentOrLast +import io.github.wulkanowy.utils.init +import kotlinx.coroutines.withTimeout +import timber.log.Timber +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.time.Duration.Companion.seconds + +@Singleton +class SchoolsRepository @Inject constructor( + private val integrityHelper: IntegrityHelper, + private val schoolsService: SchoolsService, + private val sdk: Sdk, +) { + + suspend fun logSchoolLogin(loginData: LoginData, students: List) { + students.forEach { + runCatching { + withTimeout(10.seconds) { + logLogin(loginData, it.student, it.semesters.getCurrentOrLast()) + } + } + .onFailure { Timber.e(it) } + } + } + + private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) { + val requestId = UUID.randomUUID().toString() + val token = integrityHelper.getIntegrityToken(requestId) ?: return + + val schoolInfo = sdk + .init(student.copy(password = loginData.password)) + .switchDiary( + diaryId = semester.diaryId, + kindergartenDiaryId = semester.kindergartenDiaryId, + schoolYear = semester.schoolYear + ) + .getSchool() + + schoolsService.logLoginEvent( + IntegrityRequest( + tokenString = token, + data = LoginEvent( + uuid = requestId, + schoolAddress = schoolInfo.address, + schoolName = schoolInfo.name, + schoolShort = student.schoolShortName, + scraperBaseUrl = student.scrapperBaseUrl, + loginType = student.loginType, + symbol = student.symbol, + schoolId = student.schoolSymbol, + ) + ) + ) + } +} 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 70862e828..0da86e56e 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 @@ -9,6 +9,7 @@ import io.github.wulkanowy.data.pojos.RegisterStudent import io.github.wulkanowy.data.pojos.RegisterSymbol import io.github.wulkanowy.data.pojos.RegisterUnit import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException @@ -26,6 +27,7 @@ import javax.inject.Inject class LoginStudentSelectPresenter @Inject constructor( studentRepository: StudentRepository, + private val schoolsRepository: SchoolsRepository, private val loginErrorHandler: LoginErrorHandler, private val syncManager: SyncManager, private val analytics: AnalyticsHelper, @@ -236,17 +238,20 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun registerStudents(students: List) { - val studentsWithSemesters = students - .filterIsInstance().map { item -> - item.student.mapToStudentWithSemesters( - user = registerUser, - symbol = item.symbol, - scrapperDomainSuffix = loginData.domainSuffix, - unit = item.unit, - colors = appInfo.defaultColorsForAvatar, - ) - } - resourceFlow { studentRepository.saveStudents(studentsWithSemesters) } + val filteredStudents = students.filterIsInstance() + val studentsWithSemesters = filteredStudents.map { item -> + item.student.mapToStudentWithSemesters( + user = registerUser, + symbol = item.symbol, + scrapperDomainSuffix = loginData.domainSuffix, + unit = item.unit, + colors = appInfo.defaultColorsForAvatar, + ) + } + resourceFlow { + studentRepository.saveStudents(studentsWithSemesters) + schoolsRepository.logSchoolLogin(loginData, studentsWithSemesters) + } .logResourceStatus("registration") .onEach { when (it) { @@ -254,11 +259,13 @@ class LoginStudentSelectPresenter @Inject constructor( showProgress(true) showContent(false) } + is Resource.Success -> { syncManager.startOneTimeSyncWorker(quiet = true) view?.navigateToNext() logRegisterEvent(studentsWithSemesters) } + is Resource.Error -> { view?.apply { showProgress(false) diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt index 962e5b208..e16db53c6 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AppInfo.kt @@ -25,7 +25,8 @@ open class AppInfo @Inject constructor() { open val systemModel: String get() = Build.MODEL - open val messagesBaseUrl = BuildConfig.MESSAGES_BASE_URL + open val messagesBaseUrl: String = BuildConfig.MESSAGES_BASE_URL + open val schoolsBaseUrl: String = BuildConfig.SCHOOLS_BASE_URL @Suppress("DEPRECATION") open val systemLanguage: String diff --git a/app/src/play/java/io/github/wulkanowy/utils/IntegrityHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/IntegrityHelper.kt new file mode 100644 index 000000000..41df0487a --- /dev/null +++ b/app/src/play/java/io/github/wulkanowy/utils/IntegrityHelper.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.utils + +import android.content.Context +import com.google.android.play.core.integrity.IntegrityManagerFactory +import com.google.android.play.core.integrity.IntegrityTokenRequest +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.tasks.await +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class IntegrityHelper @Inject constructor( + @ApplicationContext private val context: Context, +) { + + suspend fun getIntegrityToken(nonce: String): String? { + val integrityManager = IntegrityManagerFactory.create(context) + + val integrityTokenResponse = integrityManager.requestIntegrityToken( + IntegrityTokenRequest.builder() + .setNonce(nonce) + .build() + ) + return integrityTokenResponse.await().token() + } +} 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 06aabec72..fad6436d8 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 @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.pojos.RegisterStudent import io.github.wulkanowy.data.pojos.RegisterSymbol import io.github.wulkanowy.data.pojos.RegisterUnit import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.scrapper.Scrapper @@ -40,6 +41,9 @@ class LoginStudentSelectPresenterTest { @MockK lateinit var studentRepository: StudentRepository + @MockK + lateinit var schoolsRepository: SchoolsRepository + @MockK(relaxed = true) lateinit var analytics: AnalyticsHelper @@ -110,6 +114,7 @@ class LoginStudentSelectPresenterTest { clearMocks(studentRepository, loginStudentSelectView) coEvery { studentRepository.getSavedStudents(false) } returns emptyList() + coEvery { schoolsRepository.logSchoolLogin(any(), any()) } just Runs every { loginStudentSelectView.initView() } just Runs every { loginStudentSelectView.symbols } returns emptyMap() @@ -120,6 +125,7 @@ class LoginStudentSelectPresenterTest { presenter = LoginStudentSelectPresenter( studentRepository = studentRepository, + schoolsRepository = schoolsRepository, loginErrorHandler = errorHandler, syncManager = syncManager, analytics = analytics, From 4d3b16ec8073285f724448f4e73dbe5b75b9e786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 21:01:25 +0200 Subject: [PATCH 285/545] Fix password toggle icon tint after clearing error (#2309) --- .../wulkanowy/ui/modules/login/form/LoginFormFragment.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 ff7fd864b..aa2da3114 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 @@ -184,7 +184,9 @@ class LoginFormFragment : BaseFragment(R.layout.fragme override fun clearPassError() { binding.loginFormPassLayout.error = null - binding.loginFormPassLayout.setEndIconTintList(null) + binding.loginFormPassLayout.setEndIconTintList( + requireContext().getAttrColorStateList(R.attr.colorOnSurface) + ) binding.loginFormErrorBox.isVisible = false } From 183544646846be7f6fd1c160825b3a07e8c4e39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 21:01:59 +0200 Subject: [PATCH 286/545] Fix password reset related issues (#2308) * Fix login hint in password reset field * Don't hide first password reset button * Change recover button label --- .../wulkanowy/ui/modules/login/form/LoginFormFragment.kt | 3 +-- .../ui/modules/login/recover/LoginRecoverPresenter.kt | 4 ++-- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) 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 aa2da3114..0a2ea5d6d 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 @@ -238,8 +238,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme } override fun showContact(show: Boolean) { - binding.loginFormContact.visibility = if (show) VISIBLE else GONE - binding.loginFormRecoverLink.visibility = if (show) GONE else VISIBLE + binding.loginFormContact.isVisible = show } override fun openPrivacyPolicyPage() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 3d0493012..a424df40d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -46,7 +46,7 @@ class LoginRecoverPresenter @Inject constructor( fun updateFields() { view?.run { - setUsernameHint(if ("standard" in recoverHostValue) emailHintString else loginPeselEmailHintString) + setUsernameHint(if ("email" in recoverHostValue) emailHintString else loginPeselEmailHintString) } } @@ -92,7 +92,7 @@ class LoginRecoverPresenter @Inject constructor( isCorrect = false } - if ("standard" in host && "@" !in username) { + if ("email" in host && "@" !in username) { view?.setUsernameError(view?.invalidEmailString.orEmpty()) isCorrect = false } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce277bdce..7da41b3d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,7 +76,7 @@ Zgłoszenie: Problemy z logowaniem Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość: Make sure you select the correct UONET+ register variation! - I forgot my password + Reset password Recover your account Recover Student is already signed in From 26a95ecb995f701cd9eb7184499c7467c4c4527d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 21:02:36 +0200 Subject: [PATCH 287/545] Auto select students for login (#2307) * Auto select students for login * Add sign in icon to sign in button on student select screen --- .../LoginStudentSelectPresenter.kt | 25 ++++++++++++++++++- app/src/main/res/drawable/ic_login.xml | 5 ++++ .../layout/fragment_login_student_select.xml | 2 ++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_login.xml 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 0da86e56e..4e5cd5108 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 @@ -21,6 +21,7 @@ import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.ifNullOrBlank +import io.github.wulkanowy.utils.isCurrent import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -73,7 +74,14 @@ class LoginStudentSelectPresenter @Inject constructor( students = it.dataOrNull.orEmpty() when (it) { is Resource.Loading -> Timber.d("Login student select students load started") - is Resource.Success -> refreshItems() + is Resource.Success -> { + getStudentsWithCurrentlyActiveSemesters() + selectedStudents.clear() + selectedStudents.addAll(getStudentsWithCurrentlyActiveSemesters()) + view?.enableSignIn(selectedStudents.isNotEmpty()) + refreshItems() + } + is Resource.Error -> { errorHandler.dispatch(it.error) lastError = it.error @@ -83,6 +91,21 @@ class LoginStudentSelectPresenter @Inject constructor( }.launch() } + private fun getStudentsWithCurrentlyActiveSemesters(): List { + val students = registerUser.symbols.flatMap { symbol -> + symbol.schools.flatMap { unit -> + unit.students.map { + createStudentItem(it, symbol, unit, students) + } + } + } + return students.filter { student -> + student.student.semesters.any { semester -> + semester.isCurrent() + } + } + } + private fun createItems(): List = buildList { val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } diff --git a/app/src/main/res/drawable/ic_login.xml b/app/src/main/res/drawable/ic_login.xml new file mode 100644 index 000000000..57aef3dca --- /dev/null +++ b/app/src/main/res/drawable/ic_login.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index c47b9ae35..04c808857 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -56,6 +56,8 @@ android:layout_marginBottom="32dp" android:enabled="false" android:text="@string/login_sign_in" + app:icon="@drawable/ic_login" + app:iconGravity="end" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/loginStudentSelectRecycler" /> From 58d5196ac9847ff7db7033e9df95c8febf27b9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 22:25:23 +0200 Subject: [PATCH 288/545] Improve symbol input field (#2312) --- .../ui/modules/login/symbol/LoginSymbolFragment.kt | 7 +++++++ .../ui/modules/login/symbol/LoginSymbolPresenter.kt | 11 +++++++++++ .../ui/modules/login/symbol/LoginSymbolView.kt | 2 ++ app/src/main/res/layout/fragment_login_symbol.xml | 4 +++- app/src/main/res/values/strings.xml | 4 +++- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 692aaeb76..679576599 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -100,6 +100,13 @@ class LoginSymbolFragment : } } + override fun setErrorSymbolDefinitelyInvalid() { + with(binding.loginSymbolNameLayout) { + requestFocus() + error = getString(R.string.login_invalid_symbol_definitely) + } + } + override fun setErrorSymbolRequire() { setErrorSymbol(getString(R.string.error_field_required)) } 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 91fe1ac3c..01855fcec 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 @@ -53,6 +53,10 @@ class LoginSymbolPresenter @Inject constructor( view?.setErrorSymbolRequire() return } + if (isFormDefinitelyInvalid()) { + view?.setErrorSymbolDefinitelyInvalid() + return + } loginData = loginData.copy( symbol = view?.symbolValue?.getNormalizedSymbol(), @@ -130,6 +134,13 @@ class LoginSymbolPresenter @Inject constructor( }.launch("login") } + private fun isFormDefinitelyInvalid(): Boolean { + val definitelyInvalidSymbols = listOf("vulcan", "uonet", "wulkanowy", "standardowa") + val normalizedSymbol = view?.symbolValue.orEmpty().getNormalizedSymbol() + + return normalizedSymbol in definitelyInvalidSymbols + } + fun onFaqClick() { view?.openFaqPage() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 6585c00f4..3c10d5744 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -18,6 +18,8 @@ interface LoginSymbolView : BaseView { fun setErrorSymbolInvalid() + fun setErrorSymbolDefinitelyInvalid() + fun setErrorSymbolRequire() fun setErrorSymbol(message: String) diff --git a/app/src/main/res/layout/fragment_login_symbol.xml b/app/src/main/res/layout/fragment_login_symbol.xml index d928d65fd..37f4c731a 100644 --- a/app/src/main/res/layout/fragment_login_symbol.xml +++ b/app/src/main/res/layout/fragment_login_symbol.xml @@ -141,7 +141,9 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/loginSymbolHelper"> + app:layout_constraintTop_toBottomOf="@+id/loginSymbolHelper" + app:placeholderText="@string/login_symbol_placeholder" + app:placeholderTextColor="?colorTertiary"> Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Sign in Password too short Login details are incorrect @@ -59,7 +60,8 @@ Invalid email Use the assigned login instead of email Use the assigned login or email in @%1$s - Invalid symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school 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 From 711de0f77f0954d78b70c2e7d5689aa8bada7919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 22:26:19 +0200 Subject: [PATCH 289/545] Fix average calculation when there is no real semesters available (#2310) --- .../github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 2d63aae4d..ec4bd8e8c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -58,7 +58,7 @@ class GradeAverageProvider @Inject constructor( when (params.gradeAverageMode) { ONE_SEMESTER -> getGradeSubjects( student = student, - semester = semesters.single { it.semesterId == semesterId }, + semester = semesters.first { it.semesterId == semesterId }, forceRefresh = forceRefresh, params = params, ) From fca69e723427dbb67f90cc32bb8be09dad99903a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 22:27:08 +0200 Subject: [PATCH 290/545] Add form dialog to login e-mail support (#2306) --- .../modules/login/form/LoginFormFragment.kt | 19 +-- .../modules/login/form/LoginFormPresenter.kt | 41 ++++-- .../ui/modules/login/form/LoginFormView.kt | 3 +- .../LoginStudentSelectFragment.kt | 20 +-- .../LoginStudentSelectPresenter.kt | 33 ++--- .../studentselect/LoginStudentSelectView.kt | 3 +- .../login/support/LoginSupportDialog.kt | 133 ++++++++++++++++++ .../modules/login/support/LoginSupportInfo.kt | 12 ++ .../login/symbol/LoginSymbolFragment.kt | 25 ++-- .../login/symbol/LoginSymbolPresenter.kt | 19 ++- .../modules/login/symbol/LoginSymbolView.kt | 3 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 1 - .../main/res/layout/dialog_login_support.xml | 92 ++++++++++++ .../main/res/layout/fragment_dashboard.xml | 5 +- app/src/main/res/values/strings.xml | 8 +- app/src/main/res/values/styles.xml | 1 - 16 files changed, 322 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt create mode 100644 app/src/main/res/layout/dialog_login_support.xml 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 0a2ea5d6d..8e9b86fa3 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 @@ -17,6 +17,8 @@ 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.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.* import javax.inject.Inject @@ -282,20 +284,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme presenter.updateCustomDomainSuffixVisibility() } - override fun openEmail(lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString( - R.string.login_email_text, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", - appInfo.systemVersion.toString(), - "${appInfo.versionName}-${appInfo.buildFlavor}", - "$formHostValue/$formHostSymbol", - preferencesRepository.installationId, - lastError - ) - ) + override fun openEmail(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } } 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 4e0404d9b..ad535c382 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 @@ -17,6 +17,7 @@ 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 +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.ifNullOrBlank @@ -133,7 +134,7 @@ class LoginFormPresenter @Inject constructor( } } - fun onSignInClick() { + private fun getLoginData(): LoginData { val email = view?.formUsernameValue.orEmpty().trim() val password = view?.formPassValue.orEmpty().trim() val host = view?.formHostValue.orEmpty().trim() @@ -142,15 +143,27 @@ class LoginFormPresenter @Inject constructor( }.orEmpty() val symbol = view?.formHostSymbol.orEmpty().trim() - if (!validateCredentials(email, password, host)) return + return LoginData( + login = email, + password = password, + baseUrl = host, + domainSuffix = domainSuffix, + symbol = symbol + ) + } + + fun onSignInClick() { + val loginData = getLoginData() + + if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return resourceFlow { studentRepository.getUserSubjectsFromScrapper( - email = email, - password = password, - scrapperBaseUrl = host, - domainSuffix = domainSuffix, - symbol = symbol + email = loginData.login, + password = loginData.password, + scrapperBaseUrl = loginData.baseUrl, + domainSuffix = loginData.domainSuffix, + symbol = loginData.symbol.orEmpty(), ) } .logResourceStatus("login") @@ -162,7 +175,6 @@ class LoginFormPresenter @Inject constructor( } } .onResourceSuccess { - val loginData = LoginData(email, password, host, domainSuffix, symbol) when (it.symbols.size) { 0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(loginData, it) @@ -170,7 +182,7 @@ class LoginFormPresenter @Inject constructor( analytics.logEvent( "registration_form", "success" to true, - "scrapperBaseUrl" to host, + "scrapperBaseUrl" to loginData.baseUrl, "error" to "No error" ) } @@ -187,7 +199,7 @@ class LoginFormPresenter @Inject constructor( analytics.logEvent( "registration_form", "success" to false, - "scrapperBaseUrl" to host, + "scrapperBaseUrl" to loginData.baseUrl, "error" to it.message.ifNullOrBlank { "No message" } ) } @@ -199,7 +211,14 @@ class LoginFormPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(lastError?.message.ifNullOrBlank { "none" }) + view?.openEmail( + LoginSupportInfo( + loginData = getLoginData(), + lastErrorMessage = lastError?.message, + registerUser = null, + enteredSymbol = null, + ) + ) } fun onRecoverClick() { 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 5b4dcadfe..f2b7b1003 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 @@ -4,6 +4,7 @@ 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 +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginFormView : BaseView { @@ -79,7 +80,7 @@ interface LoginFormView : BaseView { fun openFaqPage() - fun openEmail(lastError: String) + fun openEmail(supportInfo: LoginSupportInfo) fun openAdvancedLogin() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index c33d12faa..06efd8d98 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -10,8 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser @@ -106,21 +109,8 @@ class LoginStudentSelectFragment : context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) } - override fun openEmail(lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString( - R.string.login_email_text, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", - appInfo.systemVersion.toString(), - "${appInfo.versionName}-${appInfo.buildFlavor}", - "Select users to log in", - preferencesRepository.installationId, - lastError - ) - ) + override fun openEmail(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } override fun onDestroyView() { 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 4e5cd5108..cc9e422ed 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 @@ -12,15 +12,14 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow -import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.ifNullOrBlank import io.github.wulkanowy.utils.isCurrent import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -311,28 +310,14 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun onEmailClick() { - view?.openEmail(lastError?.message.ifNullOrBlank { - loginData.baseUrl + "/" + loginData.symbol + "\n" + registerUser.symbols.filterNot { - (it.error is AccountPermissionException || it.error is InvalidSymbolException) && it.symbol != loginData.symbol - }.joinToString(";\n") { symbol -> - buildString { - append(" -") - append(symbol.symbol) - append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})") - if (symbol.schools.isNotEmpty()) { - append(": ") - } - append(symbol.schools.joinToString(", ") { unit -> - buildString { - append(unit.schoolShortName) - append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})") - } - }) - } - } + "\nPozostałe: " + registerUser.symbols.filter { - it.error is AccountPermissionException || it.error is InvalidSymbolException - }.joinToString(", ") { it.symbol } - }) + view?.openEmail( + LoginSupportInfo( + loginData = loginData, + registerUser = registerUser, + lastErrorMessage = lastError?.message, + enteredSymbol = loginData.symbol, + ) + ) } private fun logRegisterEvent( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index 39f312bf3..b69700f17 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginStudentSelectView : BaseView { @@ -23,5 +24,5 @@ interface LoginStudentSelectView : BaseView { fun openDiscordInvite() - fun openEmail(lastError: String) + fun openEmail(supportInfo: LoginSupportInfo) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt new file mode 100644 index 000000000..d2b1d2ce6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt @@ -0,0 +1,133 @@ +package io.github.wulkanowy.ui.modules.login.support + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.databinding.DialogLoginSupportBinding +import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException +import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException +import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.openEmailClient +import io.github.wulkanowy.utils.serializable +import javax.inject.Inject + +@AndroidEntryPoint +class LoginSupportDialog : BaseDialogFragment() { + + @Inject + lateinit var appInfo: AppInfo + + @Inject + lateinit var preferencesRepository: PreferencesRepository + + private lateinit var supportInfo: LoginSupportInfo + + companion object { + private const val ARGUMENT_KEY = "info" + + fun newInstance(info: LoginSupportInfo) = LoginSupportDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to info) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_FRAME, R.style.WulkanowyTheme_NoActionBar) + supportInfo = requireArguments().serializable(ARGUMENT_KEY) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = DialogLoginSupportBinding.inflate(inflater) + .apply { binding = this } + binding.dialogLoginSupportToolbar.setNavigationOnClickListener { dismiss() } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + dialogLoginSupportSchoolInput.doOnTextChanged { _, _, _, _ -> + with(dialogLoginSupportSchoolLayout) { + isErrorEnabled = false + error = null + } + } + dialogLoginSupportSubmit.setOnClickListener { + if (dialogLoginSupportSchoolInput.text.isNullOrBlank()) { + with(dialogLoginSupportSchoolLayout) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } else { + onSubmitClick() + dismiss() + } + } + } + } + + private fun onSubmitClick() { + with(binding) { + context?.openEmailClient( + chooserTitle = requireContext().getString(R.string.login_email_intent_title), + email = "wulkanowyinc@gmail.com", + subject = requireContext().getString(R.string.login_email_subject), + body = requireContext().getString( + R.string.login_email_text, + "${appInfo.systemManufacturer} ${appInfo.systemModel}", + appInfo.systemVersion.toString(), + "${appInfo.versionName}-${appInfo.buildFlavor}", + supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol, + preferencesRepository.installationId, + getLastErrorFromStudentSelectScreen(), + dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() } + ?: return@with, + dialogLoginSupportAdditionalInput.text, + ) + ) + } + } + + private fun getLastErrorFromStudentSelectScreen(): String { + if (!supportInfo.lastErrorMessage.isNullOrBlank()) { + return supportInfo.lastErrorMessage!! + } + if (supportInfo.registerUser?.symbols.isNullOrEmpty()) { + return "" + } + + return "\n" + supportInfo.registerUser?.symbols?.filterNot { + (it.error is AccountPermissionException || it.error is InvalidSymbolException) && + it.symbol != supportInfo.enteredSymbol + }?.joinToString(";\n") { symbol -> + buildString { + append(" -") + append(symbol.symbol) + append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})") + if (symbol.schools.isNotEmpty()) { + append(": ") + } + append(symbol.schools.joinToString(", ") { unit -> + buildString { + append(unit.schoolShortName) + append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})") + } + }) + } + } + "\nPozostałe: " + supportInfo.registerUser?.symbols?.filter { + it.error is AccountPermissionException || it.error is InvalidSymbolException + }?.joinToString(", ") { it.symbol } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt new file mode 100644 index 000000000..3f101dd63 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportInfo.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.ui.modules.login.support + +import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.ui.modules.login.LoginData +import java.io.Serializable + +data class LoginSupportInfo( + val loginData: LoginData, + val registerUser: RegisterUser?, + val lastErrorMessage: String?, + val enteredSymbol: String?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 679576599..23ebffe9d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -18,7 +18,13 @@ import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.hideSoftInput +import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.serializable +import io.github.wulkanowy.utils.showSoftInput import javax.inject.Inject @AndroidEntryPoint @@ -170,20 +176,7 @@ class LoginSymbolFragment : ) } - override fun openEmail(host: String, lastError: String) { - context?.openEmailClient( - chooserTitle = requireContext().getString(R.string.login_email_intent_title), - email = "wulkanowyinc@gmail.com", - subject = requireContext().getString(R.string.login_email_subject), - body = requireContext().getString( - R.string.login_email_text, - "${appInfo.systemManufacturer} ${appInfo.systemModel}", - appInfo.systemVersion.toString(), - "${appInfo.versionName}-${appInfo.buildFlavor}", - "$host/${binding.loginSymbolName.text}", - preferencesRepository.installationId, - lastError - ) - ) + override fun openSupportDialog(supportInfo: LoginSupportInfo) { + LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } } 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 01855fcec..02bfde5d7 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 @@ -11,6 +11,7 @@ import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach @@ -78,6 +79,7 @@ class LoginSymbolPresenter @Inject constructor( showProgress(true) showContent(false) } + is Resource.Success -> { when (user.data.symbols.size) { 0 -> { @@ -87,6 +89,7 @@ class LoginSymbolPresenter @Inject constructor( showContact(true) } } + else -> { val enteredSymbolDetails = user.data.symbols .firstOrNull() @@ -111,6 +114,7 @@ class LoginSymbolPresenter @Inject constructor( "error" to "No error" ) } + is Resource.Error -> { Timber.i("Login with symbol result: An exception occurred") analytics.logEvent( @@ -146,12 +150,13 @@ class LoginSymbolPresenter @Inject constructor( } fun onEmailClick() { - view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { - registerUser?.symbols?.flatMap { symbol -> - symbol.schools.map { it.error?.message } + symbol.error?.message - }?.filterNotNull()?.distinct()?.joinToString(";") { - it.take(46) + "..." - } ?: "blank" - }) + view?.openSupportDialog( + LoginSupportInfo( + loginData = loginData, + registerUser = registerUser, + lastErrorMessage = lastError?.message, + enteredSymbol = view?.symbolValue, + ) + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index 3c10d5744..ace12f780 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.login.symbol import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData +import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo interface LoginSymbolView : BaseView { @@ -42,5 +43,5 @@ interface LoginSymbolView : BaseView { fun openFaqPage() - fun openEmail(host: String, lastError: String) + fun openSupportDialog(supportInfo: LoginSupportInfo) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 091080a55..178d6e94b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -119,7 +119,6 @@ class MainActivity : BaseActivity(), MainVie //https://developer.android.com/guide/playcore/in-app-updates#status_callback @Deprecated("Deprecated in Java") - @Suppress("DEPRECATION") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) updateHelper.onActivityResult(requestCode, resultCode) diff --git a/app/src/main/res/layout/dialog_login_support.xml b/app/src/main/res/layout/dialog_login_support.xml new file mode 100644 index 000000000..c04d78120 --- /dev/null +++ b/app/src/main/res/layout/dialog_login_support.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index 30f85446a..348602d77 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -26,7 +26,8 @@ android:layout_height="match_parent" android:clipToPadding="false" android:paddingTop="6dp" - android:paddingBottom="6dp" /> + android:paddingBottom="6dp" + tools:listitem="@layout/item_dashboard_grades" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df40fe707..08e3ebe61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,7 +76,7 @@ Discord Send email Zgłoszenie: Problemy z logowaniem - Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość: + Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość: %7$s\nDodatkowe informacje: %8$s Make sure you select the correct UONET+ register variation! Reset password Recover your account @@ -86,6 +86,12 @@ Other search locations No active students found Enter a different symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 603e22abc..a0023dda1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -82,7 +82,6 @@ From 4d085f826652b7fd9a04fa1ddbf8cae3d3a97a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 26 Sep 2023 23:04:27 +0200 Subject: [PATCH 291/545] New Crowdin updates (#2311) --- app/src/main/res/values-cs/strings.xml | 12 ++++++++++-- app/src/main/res/values-da-rDK/strings.xml | 12 ++++++++++-- app/src/main/res/values-de/strings.xml | 12 ++++++++++-- app/src/main/res/values-es-rES/strings.xml | 12 ++++++++++-- app/src/main/res/values-it-rIT/strings.xml | 12 ++++++++++-- app/src/main/res/values-pl/strings.xml | 12 ++++++++++-- app/src/main/res/values-ru/strings.xml | 12 ++++++++++-- app/src/main/res/values-sk/strings.xml | 12 ++++++++++-- app/src/main/res/values-uk/strings.xml | 12 ++++++++++-- 9 files changed, 90 insertions(+), 18 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e9e049326..a365942a8 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Přihlásit Toto heslo je příliš krátké Přihlašovací údaje jsou nesprávné @@ -54,7 +55,8 @@ Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Použijte přiřazené přihlašovací nebo e-mail v @%1$s - Neplatný symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Vybraný žák je už přihlášen Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli Variace deníku UONET+ na první přihlašovací obrazovce @@ -69,7 +71,7 @@ Discord Poslat e-mail Ujistěte se, že jste vybrali správnou variantu deníku UONET+! - Zapomněl jsem své heslo + Reset password Obnovte svůj účet Obnovit Žák je už přihlášen @@ -77,6 +79,12 @@ Jiná místa vyhledávání Nebyli nalezeni žádní aktivní žáci Zadejte jiný symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Povolit oznámení Povolit upozornění, abyste nezmeškali zprávu od učitele nebo o nové známce diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 2abf1a4a6..c8846684b 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Sign in Password too short Login details are incorrect @@ -54,7 +55,8 @@ Invalid email Use the assigned login instead of email Use the assigned login or email in @%1$s - Invalid symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school 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 @@ -69,7 +71,7 @@ Discord Send email Make sure you select the correct UONET+ register variation! - I forgot my password + Reset password Recover your account Recover Student is already signed in @@ -77,6 +79,12 @@ Other search locations No active students found Enter a different symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Enable notifications Enable notifications so you don\'t miss message from teacher or new grade diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f08a504a5..a909a6b80 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Anmelden Passwort ist zu kurz Anmeldedaten sind falsch @@ -54,7 +55,8 @@ Ungültige email Den zugewiesenen Login anstelle von email verwenden Benutze den zugewiesenen Login oder E-Mail in @%1$s - Ungültige symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers Ausgewählter Student ist bereits angemeldet. 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 @@ -69,7 +71,7 @@ Discord email senden Stellen Sie sicher, dass Sie die richtige UONET+ Registervariation wählen! - Ich habe mein Passwort vergessen. + Reset password Ihr Konto wiederherstellen Wiederherstellen Student ist bereits angemeldet @@ -77,6 +79,12 @@ Andere Suchorte Keine aktiven Schüler gefunden Geben Sie ein anderes Symbol ein + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Benachrichtigungen aktivieren Aktivieren Sie Benachrichtigungen, damit Sie keine Nachricht vom Lehrer oder eine neue Klasse verpassen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 2abf1a4a6..c8846684b 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Sign in Password too short Login details are incorrect @@ -54,7 +55,8 @@ Invalid email Use the assigned login instead of email Use the assigned login or email in @%1$s - Invalid symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school 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 @@ -69,7 +71,7 @@ Discord Send email Make sure you select the correct UONET+ register variation! - I forgot my password + Reset password Recover your account Recover Student is already signed in @@ -77,6 +79,12 @@ Other search locations No active students found Enter a different symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Enable notifications Enable notifications so you don\'t miss message from teacher or new grade diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 2abf1a4a6..c8846684b 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Sign in Password too short Login details are incorrect @@ -54,7 +55,8 @@ Invalid email Use the assigned login instead of email Use the assigned login or email in @%1$s - Invalid symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school 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 @@ -69,7 +71,7 @@ Discord Send email Make sure you select the correct UONET+ register variation! - I forgot my password + Reset password Recover your account Recover Student is already signed in @@ -77,6 +79,12 @@ Other search locations No active students found Enter a different symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Enable notifications Enable notifications so you don\'t miss message from teacher or new grade diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a2b5510ec..4ed2facc8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + Np. \"lodz\" czy \"powiatjaroslawski\" Zaloguj To hasło jest za krótkie Dane logowania są niepoprawne @@ -54,7 +55,8 @@ Nieprawidłowy adres e-mail Użyj loginu zamiast adresu e-mail Użyj loginu lub adresu e-mail w @%1$s - Nieprawidłowy symbol + Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą + Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+ Wybrany uczeń jest już zalogowany Symbol można znaleźć na stronie dziennika w Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUpewnij się, że ustawiłeś odpowiednią odmianę dziennika w polu Odmiana dziennika UONET+ na pierwszym ekranie logowania @@ -69,7 +71,7 @@ Discord Wyślij wiadomość e-mail Upewnij się, że została wybrana odpowiednia odmiana dziennika UONET+! - Nie pamiętam hasła + Zresetuj hasło Przywróć swoje konto Przywróć Uczeń jest już zalogowany @@ -77,6 +79,12 @@ Inne lokalizacje wyszukiwania Nie znaleziono aktywnych uczniów Wprowadź inny symbol + Uzyskaj pomoc + Pełna nazwa szkoły (wymagana) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Dodatkowe informacje (po polsku) (nieobowiązkowo) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Wyślij Włącz powiadomienia Włącz powiadomienia, aby nie przegapić wiadomości od nauczyciela lub nowej oceny diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d075dac6c..841fa9f90 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Войти Пароль слишком короткий Данные для входа указаны неверно @@ -54,7 +55,8 @@ Неверный e-mail Используйте назначенный логин вместо e-mail Используйте назначенный логин или email в @%1$s - Неверный symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+ Данный ученик уже авторизован Symbol можно найти на странице регистрации в  Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nУбедитесь, что вы выбрали соответствующий тип дневника в поле Тип дневника UONET+ на первом экране входа @@ -69,7 +71,7 @@ Discord Отправить письмо Убедитесь, что вы выбрали правильный тип дневника UONET+ - Забыли пароль? + Reset password Восстановите свой аккаунт Восстановить Ученик уже авторизован @@ -77,6 +79,12 @@ Другие места поиска Не найдено активных учеников Введите другой symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Включить уведомления Включить уведомления, чтобы вы не пропустили сообщение от учителя или новую оценку diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d059927eb..018806b68 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Prihlásiť Toto heslo je príliš krátke Prihlasovacie údaje sú nesprávne @@ -54,7 +55,8 @@ Neplatný e-mail Namiesto e-mailu použite priradené prihlasovacie údaje Použite priradené prihlasovacie alebo e-mail v @%1$s - Neplatný symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ Vybraný žiak už je prihlásený Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUistite sa, že ste nastavili správny variant denníka v poli Variácia denníka UONET+ na prvej prihlasovacej obrazovke @@ -69,7 +71,7 @@ Discord Poslať e-mail Uistite sa, že ste vybrali správny variant denníka UONET+! - Zabudol som heslo + Reset password Obnovte svoj účet Obnoviť Žiak je už prihlásený @@ -77,6 +79,12 @@ Iné miesta vyhľadávania Neboli nájdení žiadni aktívni žiaci Zadajte iný symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Povoliť oznámenia Povoliť oznámenia, aby ste nezmeškali správu od učiteľa alebo o novej známke diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b46b9afe9..a05634cff 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -44,6 +44,7 @@ Token PIN Symbol + E.g. \"lodz\" or \"powiatjaroslawski\" Увійти Занадто короткий пароль Вказані невірні дані @@ -54,7 +55,8 @@ Недійсна адреса e-mail Використовуйте призначений логін замість адреси e-mail Використовуйте призначений логін або адресу e-mail в @%1$s - Неправильний symbol + Invalid symbol. If you cannot find it, please contact the school + Don\'t make this up! If you cannot find it, please contact the school Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+ Цього учня вже авторизовано Symbol можно знайти на сторінці щоденника у Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nПереконайтеся, що ви вказали відповідний щоденник у полі Тип щоденника UONET+ на першому екрані логування @@ -69,7 +71,7 @@ Discord Надіслати електронний лист Переконайтеся, що ви вибрали правильний тип щоденника UONET+! - Забули пароль? + Reset password Відновіть свій обліковий запис Відновити Учня вже авторизовано @@ -77,6 +79,12 @@ Інші розташування пошуку Активних учнів не знайдено Введіть інший symbol + Get help + Full school name (required) + Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Additional information in Polish (optional) + Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" + Submit Увімкнути сповіщення Увімкнути сповіщення, щоб не пропустити лист від вчителя або нову оцінку From 4a2bf539f0f4bc0f0a21e3e733b227e787e130ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Sep 2023 23:13:32 +0200 Subject: [PATCH 292/545] Version 2.2.0 --- app/build.gradle | 11 +++++------ app/src/main/play/release-notes/pl-PL/default.txt | 7 ++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d85d0a3d8..ec3ee4f34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,12 +27,11 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 131 - versionName "2.1.0" + versionCode 132 + versionName "2.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" - manifestPlaceholders = [ firebase_enabled: project.hasProperty("enableFirebase"), admob_project_id: "" @@ -162,8 +161,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.25d - updatePriority = 3 + userFraction = 0.01d + updatePriority = 1 enabled.set(false) } @@ -193,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.1.0' + implementation 'io.github.wulkanowy:sdk:2.2.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 aa934ce98..898256212 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,5 @@ -Wersja 2.1.0 +Wersja 2.2.0 -— 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) +Dokonaliśmy drobnych usprawnień w obszarze logowania, które powinny pomóc Wam i nam Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From d1d665bbdffb27be240a3e1383095485a0623891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 2 Oct 2023 12:20:34 +0200 Subject: [PATCH 293/545] Fix student auto selection if there is already active some students logged (#2314) --- .github/workflows/deploy-store.yml | 4 ++-- .github/workflows/deploy-test.yml | 4 ++-- .github/workflows/test.yml | 6 +++--- .../login/studentselect/LoginStudentSelectPresenter.kt | 10 ++++++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-store.yml b/.github/workflows/deploy-store.yml index e8a220ddf..e8237a381 100644 --- a/.github/workflows/deploy-store.yml +++ b/.github/workflows/deploy-store.yml @@ -13,7 +13,7 @@ jobs: environment: google-play steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 @@ -49,7 +49,7 @@ jobs: environment: app-gallery steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index f2e9f016f..c4f55e6af 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -19,7 +19,7 @@ jobs: environment: app-center steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 @@ -89,7 +89,7 @@ jobs: if: github.event_name != 'pull_request_target' steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc4b36470..6d50c45d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - uses: fkirc/skip-duplicate-actions@master - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 @@ -45,7 +45,7 @@ jobs: - uses: fkirc/skip-duplicate-actions@master - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 @@ -71,7 +71,7 @@ jobs: - uses: fkirc/skip-duplicate-actions@master - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 17 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 cc9e422ed..6cbdfbb85 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 @@ -98,11 +98,13 @@ class LoginStudentSelectPresenter @Inject constructor( } } } - return students.filter { student -> - student.student.semesters.any { semester -> - semester.isCurrent() + return students + .filter { it.isEnabled } + .filter { student -> + student.student.semesters.any { semester -> + semester.isCurrent() + } } - } } private fun createItems(): List = buildList { From c04b3e40d256ad04bde58d35f212b6173a7fe3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 2 Oct 2023 12:21:04 +0200 Subject: [PATCH 294/545] Add negative e-mail validation in school input on support dialog (#2315) --- .../login/support/LoginSupportDialog.kt | 38 +++++++++++++------ app/src/main/res/values/strings.xml | 3 +- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt index d2b1d2ce6..fcf7f51c9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.login.support import android.os.Bundle +import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -64,21 +65,36 @@ class LoginSupportDialog : BaseDialogFragment() { error = null } } - dialogLoginSupportSubmit.setOnClickListener { - if (dialogLoginSupportSchoolInput.text.isNullOrBlank()) { - with(dialogLoginSupportSchoolLayout) { - isErrorEnabled = true - error = getString(R.string.error_field_required) - } - } else { - onSubmitClick() - dismiss() - } - } + dialogLoginSupportSubmit.setOnClickListener { onSubmitClick() } } } private fun onSubmitClick() { + when { + binding.dialogLoginSupportSchoolInput.text.isNullOrBlank() -> { + with(binding.dialogLoginSupportSchoolLayout) { + isErrorEnabled = true + error = getString(R.string.error_field_required) + } + } + + Patterns.EMAIL_ADDRESS.matcher( + binding.dialogLoginSupportSchoolInput.text.toString() + ).matches() -> { + with(binding.dialogLoginSupportSchoolLayout) { + isErrorEnabled = true + error = getString(R.string.login_support_school_invalid) + } + } + + else -> { + openEmailClientWithFilledTemplate() + dismiss() + } + } + } + + private fun openEmailClientWithFilledTemplate() { with(binding) { context?.openEmailClient( chooserTitle = requireContext().getString(R.string.login_email_intent_title), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08e3ebe61..0ba9c9970 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,8 +87,9 @@ No active students found Enter a different symbol Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit From 2cdd322ed4b5616b224b8cc9a46e4f53fcdfab1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 2 Oct 2023 12:22:02 +0200 Subject: [PATCH 295/545] Add missing class_id colum in JOIN clause of students with semesters (#2317) --- .../wulkanowy/data/db/dao/StudentDao.kt | 9 +++---- .../data/db/entities/StudentWithSemesters.kt | 5 ---- .../data/repositories/StudentRepository.kt | 24 ++++++++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index a2f0abac6..d7847c240 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.* +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar @@ -33,12 +34,12 @@ abstract class StudentDao { abstract suspend fun loadAll(): List @Transaction - @Query("SELECT * FROM Students") - abstract suspend fun loadStudentsWithSemesters(): List + @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id") + abstract suspend fun loadStudentsWithSemesters(): Map> @Transaction - @Query("SELECT * FROM Students WHERE id = :id") - abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters? + @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id") + abstract suspend fun loadStudentWithSemestersById(id: Long): Map> @Query("UPDATE Students SET is_current = 1 WHERE id = :id") abstract suspend fun updateCurrent(id: Long) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt index 9362a954e..f9869d4e2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentWithSemesters.kt @@ -1,13 +1,8 @@ package io.github.wulkanowy.data.db.entities -import androidx.room.Embedded -import androidx.room.Relation import java.io.Serializable data class StudentWithSemesters( - @Embedded val student: Student, - - @Relation(parentColumn = "student_id", entityColumn = "student_id") val semesters: List ) : Serializable 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 42d1eb84e..2e04224aa 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 @@ -62,20 +62,28 @@ class StudentRepository @Inject constructor( .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) .mapToPojo(password) - suspend fun getSavedStudents(decryptPass: Boolean = true) = - studentDb.loadStudentsWithSemesters() - .map { - it.apply { + suspend fun getSavedStudents(decryptPass: Boolean = true): List { + return studentDb.loadStudentsWithSemesters().map { (student, semesters) -> + StudentWithSemesters( + student = student.apply { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) } } - } - } + }, + semesters = semesters, + ) + } + } - suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) = - studentDb.loadStudentWithSemestersById(id)?.apply { + suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? = + studentDb.loadStudentWithSemestersById(id).let { res -> + StudentWithSemesters( + student = res.keys.firstOrNull() ?: return null, + semesters = res.values.first(), + ) + }.apply { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { decrypt(student.password) From 693ce8217d1ed871659de3009de1615ab47978af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 3 Oct 2023 00:48:37 +0200 Subject: [PATCH 296/545] New Crowdin updates (#2313) --- app/src/main/res/values-cs/strings.xml | 21 ++++++++++--------- app/src/main/res/values-da-rDK/strings.xml | 3 ++- app/src/main/res/values-de/strings.xml | 3 ++- app/src/main/res/values-es-rES/strings.xml | 3 ++- app/src/main/res/values-it-rIT/strings.xml | 3 ++- .../main/res/values-pl/preferences_values.xml | 2 +- app/src/main/res/values-pl/strings.xml | 3 ++- app/src/main/res/values-ru/strings.xml | 3 ++- app/src/main/res/values-sk/strings.xml | 21 ++++++++++--------- app/src/main/res/values-uk/strings.xml | 21 ++++++++++--------- 10 files changed, 46 insertions(+), 37 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a365942a8..1749548e1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -44,7 +44,7 @@ Token PIN Symbol - E.g. \"lodz\" or \"powiatjaroslawski\" + Např. „lodz“ nebo „powiatjaroslawski“ Přihlásit Toto heslo je příliš krátké Přihlašovací údaje jsou nesprávné @@ -55,8 +55,8 @@ Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Použijte přiřazené přihlašovací nebo e-mail v @%1$s - Invalid symbol. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school + Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu + Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Vybraný žák je už přihlášen Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli Variace deníku UONET+ na první přihlašovací obrazovce @@ -71,7 +71,7 @@ Discord Poslat e-mail Ujistěte se, že jste vybrali správnou variantu deníku UONET+! - Reset password + Obnovit heslo Obnovte svůj účet Obnovit Žák je už přihlášen @@ -79,12 +79,13 @@ Jiná místa vyhledávání Nebyli nalezeni žádní aktivní žáci Zadejte jiný symbol - Get help - Full school name (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit + Získat pomoc + Celý název školy s městem (povinný) + Např. ZSTiO Jarosław nebo SP nr 99 w Łodzi + Zadejte správný název školy + Dodatečné informace v polštině (volitelné) + Např. „Ostatnio zmieniłem szkołę i…“ (Nedávno jsem změnil školu a…) nebo „Jestem rodzicem i nie widzę drugiego dziecka…“ (Jsem rodič a nevidím žádné další dítě…) + Odeslat Povolit oznámení Povolit upozornění, abyste nezmeškali zprávu od učitele nebo o nové známce diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index c8846684b..500fba1f3 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -80,8 +80,9 @@ No active students found Enter a different symbol Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a909a6b80..d8cc92984 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -80,8 +80,9 @@ Keine aktiven Schüler gefunden Geben Sie ein anderes Symbol ein Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index c8846684b..500fba1f3 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -80,8 +80,9 @@ No active students found Enter a different symbol Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index c8846684b..500fba1f3 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -80,8 +80,9 @@ No active students found Enter a different symbol Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 8872b7ab1..2f2432e98 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -52,7 +52,7 @@ Średnia wszystkich ocen z całego roku - Nie pokauj + Nie pokazuj Tylko między lekcjami Przed i między lekcjami diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4ed2facc8..9375aeb39 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -80,8 +80,9 @@ Nie znaleziono aktywnych uczniów Wprowadź inny symbol Uzyskaj pomoc - Pełna nazwa szkoły (wymagana) + Pełna nazwa szkoły z miastem (wymagana) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Wprowadź poprawną nazwę szkoły Dodatkowe informacje (po polsku) (nieobowiązkowo) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Wyślij diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 841fa9f90..a6e45d445 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -80,8 +80,9 @@ Не найдено активных учеников Введите другой symbol Get help - Full school name (required) + Full school name with the town (required) Np. ZSTiO Jarosław lub SP nr 99 w Łodzi + Enter correct name of the school Additional information in Polish (optional) Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" Submit diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 018806b68..ed06e023e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -44,7 +44,7 @@ Token PIN Symbol - E.g. \"lodz\" or \"powiatjaroslawski\" + Napr. „lodz“ alebo „powiatjaroslawski“ Prihlásiť Toto heslo je príliš krátke Prihlasovacie údaje sú nesprávne @@ -55,8 +55,8 @@ Neplatný e-mail Namiesto e-mailu použite priradené prihlasovacie údaje Použite priradené prihlasovacie alebo e-mail v @%1$s - Invalid symbol. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school + Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu + Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ Vybraný žiak už je prihlásený Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUistite sa, že ste nastavili správny variant denníka v poli Variácia denníka UONET+ na prvej prihlasovacej obrazovke @@ -71,7 +71,7 @@ Discord Poslať e-mail Uistite sa, že ste vybrali správny variant denníka UONET+! - Reset password + Obnoviť heslo Obnovte svoj účet Obnoviť Žiak je už prihlásený @@ -79,12 +79,13 @@ Iné miesta vyhľadávania Neboli nájdení žiadni aktívni žiaci Zadajte iný symbol - Get help - Full school name (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit + Získať pomoc + Celý názov školy s mestom (povinný) + Napr. ZSTiO Jarosław alebo SP nr 99 w Łodzi + Zadajte správny názov školy + Dodatočné informácie v poľštine (voliteľné) + Napr. „Ostatnio zmieniłem szkołę i…“ (Nedávno som zmenil školu a…) alebo „Jestem rodzicem i nie widzę drugiego dziecka…“ (Som rodič a nevidím žiadne ďalšie dieťa…) + Odoslať Povoliť oznámenia Povoliť oznámenia, aby ste nezmeškali správu od učiteľa alebo o novej známke diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a05634cff..6a79263f7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -44,7 +44,7 @@ Token PIN Symbol - E.g. \"lodz\" or \"powiatjaroslawski\" + Напр. \"lodz\" чи \"powiatjaroslawski\" Увійти Занадто короткий пароль Вказані невірні дані @@ -55,8 +55,8 @@ Недійсна адреса e-mail Використовуйте призначений логін замість адреси e-mail Використовуйте призначений логін або адресу e-mail в @%1$s - Invalid symbol. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school + Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою + Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+ Цього учня вже авторизовано Symbol можно знайти на сторінці щоденника у Uczeń → Dostęp Mobilny → Wygeneruj kod dostępu.\n\nПереконайтеся, що ви вказали відповідний щоденник у полі Тип щоденника UONET+ на першому екрані логування @@ -71,7 +71,7 @@ Discord Надіслати електронний лист Переконайтеся, що ви вибрали правильний тип щоденника UONET+! - Reset password + Скинути пароль Відновіть свій обліковий запис Відновити Учня вже авторизовано @@ -79,12 +79,13 @@ Інші розташування пошуку Активних учнів не знайдено Введіть інший symbol - Get help - Full school name (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit + Отримати допомогу + Повна назва школи з містом (обов\'язково) + Напр. ZSTiO Jarosław lub SP nr 99 w Łodzi + Введіть правильну назву школи + Додаткова інформація польською мовою (за бажанням) + Напр. \"Я нещодавно змінив школу і...\" або \"Я батько і не бачу другу дитину...\" + Надіслати Увімкнути сповіщення Увімкнути сповіщення, щоб не пропустити лист від вчителя або нову оцінку From 3212efe21ed7e9cf3718cef23f606fdce9894a63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:49:05 +0000 Subject: [PATCH 297/545] Bump com.android.tools.build:gradle from 8.1.1 to 8.1.2 (#2320) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 651e9ca16..9293d1239 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.13" - classpath 'com.android.tools.build:gradle:8.1.1' + classpath 'com.android.tools.build:gradle:8.1.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.301' From c8332a064208f646f341f11dd3a5d139ff11b6f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:49:26 +0000 Subject: [PATCH 298/545] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2321) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9293d1239..33eefd889 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' 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.1.3277" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.0.3356" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From fc5ad16cb761dce29810c753af4edd3ed01bc799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 3 Oct 2023 01:14:10 +0200 Subject: [PATCH 299/545] Version 2.2.1 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ec3ee4f34..08070c5d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 33 - versionCode 132 - versionName "2.2.0" + versionCode 133 + versionName "2.2.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,7 +162,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.01d - updatePriority = 1 + updatePriority = 3 enabled.set(false) } @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.0' + implementation 'io.github.wulkanowy:sdk:2.2.1' 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 898256212..dc78c1e3d 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.2.0 +Wersja 2.2.1 -Dokonaliśmy drobnych usprawnień w obszarze logowania, które powinny pomóc Wam i nam +– dokonaliśmy kilka poprawek na ekranie logowania +– naprawiliśmy przypadek z błędnym wyświetlaniem starej klasy ucznia po zalogowaniu się na konto z nowej klasy Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 2f5577cc541c458596c5c90aac451ab0f337a2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 6 Oct 2023 10:07:55 +0200 Subject: [PATCH 300/545] Update SDK to 34 (#2322) --- app/build.gradle | 8 +++---- app/jacoco.gradle | 22 +++++++++--------- .../settings/advanced/AdvancedFragment.kt | 2 +- .../settings/advanced/AdvancedPresenter.kt | 3 ++- .../settings/appearance/AppearanceFragment.kt | 2 +- .../appearance/AppearancePresenter.kt | 3 ++- .../notifications/NotificationsFragment.kt | 2 +- .../notifications/NotificationsPresenter.kt | 3 ++- .../ui/modules/settings/sync/SyncFragment.kt | 2 +- .../ui/modules/settings/sync/SyncPresenter.kt | 6 ++++- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 5 +++- settings.gradle | 3 --- 15 files changed, 38 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 08070c5d7..b3ebb5cbe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,13 +20,13 @@ apply from: 'hooks.gradle' android { namespace 'io.github.wulkanowy' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode 133 versionName "2.2.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -199,9 +199,9 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" - implementation "androidx.core:core-ktx:1.10.1" + implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.7.2" + implementation "androidx.activity:activity-ktx:1.8.0" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.6.1" implementation "androidx.annotation:annotation:1.7.0" diff --git a/app/jacoco.gradle b/app/jacoco.gradle index f253673e6..434b9218b 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -1,16 +1,16 @@ apply plugin: "jacoco" jacoco { - toolVersion "0.8.7" + toolVersion "0.8.10" reportsDirectory.set(file("$buildDir/reports")) } -tasks.withType(Test) { +tasks.withType(Test).configureEach { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } -task jacocoTestReport(type: JacocoReport) { +tasks.register('jacocoTestReport', JacocoReport) { group = "Reporting" description = "Generate Jacoco coverage reports" @@ -33,19 +33,19 @@ task jacocoTestReport(type: JacocoReport) { '**/*_Factory.*'] classDirectories.setFrom(fileTree( - dir: "$buildDir/intermediates/classes/debug", - excludes: excludes + dir: "$buildDir/intermediates/classes/debug", + excludes: excludes ) + fileTree( - dir: "$buildDir/tmp/kotlin-classes/fdroidDebug", - excludes: excludes + dir: "$buildDir/tmp/kotlin-classes/fdroidDebug", + excludes: excludes )) sourceDirectories.setFrom(files([ - "src/main/java", - "src/fdroid/java" + "src/main/java", + "src/fdroid/java" ])) executionData.setFrom(fileTree( - dir: project.projectDir, - includes: ["**/*.exec", "**/*.ec"] + dir: project.projectDir, + includes: ["**/*.exec", "**/*.ec"] )) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index 41e9e8b1a..1b8d1a8fa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -35,7 +35,7 @@ class AdvancedFragment : PreferenceFragmentCompat(), setPreferencesFromResource(R.xml.scheme_preferences_advanced, rootKey) } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { presenter.onSharedPreferenceChanged(key) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt index d38f841fe..4bc24594a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedPresenter.kt @@ -18,7 +18,8 @@ class AdvancedPresenter @Inject constructor( Timber.i("Settings advanced view was initialized") } - fun onSharedPreferenceChanged(key: String) { + fun onSharedPreferenceChanged(key: String?) { + key ?: return Timber.i("Change settings $key") analytics.logEvent("setting_changed", "name" to key) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 493ab5d7f..70dd694cc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -39,7 +39,7 @@ class AppearanceFragment : PreferenceFragmentCompat(), setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey) } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { presenter.onSharedPreferenceChanged(key) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt index 14592a6cc..d3410ea84 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearancePresenter.kt @@ -22,7 +22,8 @@ class AppearancePresenter @Inject constructor( Timber.i("Settings appearance view was initialized") } - fun onSharedPreferenceChanged(key: String) { + fun onSharedPreferenceChanged(key: String?) { + key ?: return Timber.i("Change settings $key") preferencesRepository.apply { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 35c1faa45..af4c4e6ae 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -114,7 +114,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), setPreferencesFromResource(R.xml.scheme_preferences_notifications, rootKey) } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { presenter.onSharedPreferenceChanged(key) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt index 232b03480..b2938cf65 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsPresenter.kt @@ -38,7 +38,8 @@ class NotificationsPresenter @Inject constructor( Timber.i("Settings notifications view was initialized") } - fun onSharedPreferenceChanged(key: String) { + fun onSharedPreferenceChanged(key: String?) { + key ?: return Timber.i("Change settings $key") preferencesRepository.apply { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index df2e1348a..f48abe9ba 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -52,7 +52,7 @@ class SyncFragment : PreferenceFragmentCompat(), setPreferencesFromResource(R.xml.scheme_preferences_sync, rootKey) } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { presenter.onSharedPreferenceChanged(key) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt index 1ecb4a6ef..594e097af 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncPresenter.kt @@ -31,7 +31,8 @@ class SyncPresenter @Inject constructor( setSyncDateInView() } - fun onSharedPreferenceChanged(key: String) { + fun onSharedPreferenceChanged(key: String?) { + key ?: return Timber.i("Change settings $key") preferencesRepository.apply { @@ -52,10 +53,12 @@ class SyncPresenter @Inject constructor( Timber.i("Setting sync now started") analytics.logEvent("sync_now", "status" to "started") } + WorkInfo.State.SUCCEEDED -> { showMessage(syncSuccessString) analytics.logEvent("sync_now", "status" to "success") } + WorkInfo.State.FAILED -> { showError( syncFailedString, @@ -66,6 +69,7 @@ class SyncPresenter @Inject constructor( ) analytics.logEvent("sync_now", "status" to "failed") } + else -> Timber.d("Sync now state: ${workInfo?.state}") } if (workInfo?.state?.isFinished == true) { diff --git a/build.gradle b/build.gradle index 33eefd889..baa1f9b67 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { kotlin_version = '1.9.10' - about_libraries = '10.9.0' - hilt_version = "2.48" + about_libraries = '10.9.1' + hilt_version = '2.48.1' } repositories { mavenCentral() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 16170 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/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/settings.gradle b/settings.gradle index 16731297d..e7b4def49 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1 @@ -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' -} include ':app' From 9098e74065ebe4821694370e5c0fc8c404d52089 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:17:56 +0000 Subject: [PATCH 301/545] Bump com.google.android.material:material from 1.9.0 to 1.10.0 (#2325) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b3ebb5cbe..a591d2b2e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,7 +212,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.9.0" + implementation "com.google.android.material:material:1.10.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 516922d5aa53717d5a964161497a55e09524fa9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:18:28 +0000 Subject: [PATCH 302/545] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2324) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index baa1f9b67..3fafd2d24 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' 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.4.0.3356" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From 5dffbdadfa030f74f62c24b3157ca9c6b65f5ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 23 Oct 2023 13:04:42 +0200 Subject: [PATCH 303/545] Points statistics improvements (#2328) --- .../grade/statistics/GradeStatisticsAdapter.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index 3fce8d57e..e5f1eba0c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -22,6 +22,8 @@ import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject +import kotlin.math.max +import kotlin.math.roundToInt class GradeStatisticsAdapter @Inject constructor() : RecyclerView.Adapter() { @@ -269,7 +271,7 @@ class GradeStatisticsAdapter @Inject constructor() : valueTextSize = 12f valueTextColor = binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary) valueFormatter = object : ValueFormatter() { - override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%" + override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}" } colors = gradePointsColors } @@ -304,15 +306,20 @@ class GradeStatisticsAdapter @Inject constructor() : } xAxis.setDrawLabels(false) xAxis.setDrawGridLines(false) + + val yMaxFromValues = (max(points.others, points.student)).roundToInt() + 30f + val yMaxFromValuesWithMargin = ((yMaxFromValues / 10.0).roundToInt() * 10).toFloat() + val yMax = yMaxFromValuesWithMargin.coerceAtLeast(100f) + val yLabelCount = (yMax / 10).toInt() + 1 with(axisLeft) { axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 + axisMaximum = yMax + labelCount = yLabelCount } with(axisRight) { axisMinimum = 0f - axisMaximum = 100f - labelCount = 11 + axisMaximum = yMax + labelCount = yLabelCount } invalidate() } From 9d62410530b6dbc707634790f49857412d36db8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 23 Oct 2023 13:05:05 +0200 Subject: [PATCH 304/545] Sort teachers by name in school and teachers tab (#2327) --- .../ui/modules/schoolandteachers/teacher/TeacherPresenter.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index e2af05c92..fef06328d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -58,7 +58,10 @@ class TeacherPresenter @Inject constructor( .logResourceStatus("load teachers data") .onResourceData { view?.run { - updateData(it.filter { item -> item.name.isNotBlank() }) + updateData(it + .filter { item -> item.name.isNotBlank() } + .sortedBy { it.name } + ) showContent(it.isNotEmpty()) showEmpty(it.isEmpty()) showErrorView(false) From 83527d91f3283ad64566189ee86785775f4430a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 23 Oct 2023 13:05:46 +0200 Subject: [PATCH 305/545] Allow direct access to weekend from day navigation when there is any lesson during weekend (#2326) --- .../modules/attendance/AttendanceFragment.kt | 8 +- .../modules/attendance/AttendancePresenter.kt | 97 +++++++++++----- .../ui/modules/attendance/AttendanceView.kt | 2 + .../modules/dashboard/DashboardPresenter.kt | 2 +- .../ui/modules/timetable/TimetableFragment.kt | 9 +- .../modules/timetable/TimetablePresenter.kt | 107 +++++++++++++----- .../ui/modules/timetable/TimetableView.kt | 2 + .../main/res/layout/fragment_attendance.xml | 4 +- .../main/res/layout/fragment_timetable.xml | 4 +- 9 files changed, 172 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index a73c2606e..6e842b4d7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -148,6 +148,10 @@ class AttendanceFragment : BaseFragment(R.layout.frag binding.attendanceNavDate.text = date } + override fun showNavigation(show: Boolean) { + binding.attendanceNavContainer.isVisible = show + } + override fun clearData() { with(attendanceAdapter) { items = emptyList() @@ -281,7 +285,9 @@ class AttendanceFragment : BaseFragment(R.layout.frag override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + presenter.currentDate?.let { + outState.putLong(SAVED_DATE_KEY, it.toEpochDay()) + } } override fun onDestroyView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 26bfaf19f..f66479daf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -3,10 +3,14 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Attendance +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.repositories.AttendanceRepository 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.* @@ -14,6 +18,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDate.now import java.time.LocalDate.ofEpochDay @@ -28,9 +33,10 @@ class AttendancePresenter @Inject constructor( private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { - private var baseDate: LocalDate = now().previousOrSameSchoolDay + private var initialDate: LocalDate? = null + private var isWeekendHasLessons: Boolean = false - lateinit var currentDate: LocalDate + var currentDate: LocalDate? = null private set private lateinit var lastError: Throwable @@ -44,27 +50,34 @@ class AttendancePresenter @Inject constructor( view.initView() Timber.i("Attendance view was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - reloadView(ofEpochDay(date ?: baseDate.toEpochDay())) + currentDate = date?.let(::ofEpochDay) loadData() - if (currentDate.isHolidays) setBaseDateOnHolidays() } fun onPreviousDay() { + val date = if (isWeekendHasLessons) { + currentDate?.minusDays(1) + } else currentDate?.previousSchoolDay + view?.finishActionMode() attendanceToExcuseList.clear() - reloadView(currentDate.previousSchoolDay) + reloadView(date ?: return) loadData() } fun onNextDay() { + val date = if (isWeekendHasLessons) { + currentDate?.plusDays(1) + } else currentDate?.nextSchoolDay + view?.finishActionMode() attendanceToExcuseList.clear() - reloadView(currentDate.nextSchoolDay) + reloadView(date ?: return) loadData() } fun onPickDate() { - view?.showDatePickerDialog(currentDate) + view?.showDatePickerDialog(currentDate ?: return) } fun onDateSet(year: Int, month: Int, day: Int) { @@ -93,10 +106,8 @@ class AttendancePresenter @Inject constructor( Timber.i("Attendance view is reselected") view?.let { view -> if (view.currentStackSize == 1) { - baseDate = now().previousOrSameSchoolDay - - if (currentDate != baseDate) { - reloadView(baseDate) + if (currentDate != initialDate) { + reloadView(initialDate ?: return) loadData() } else if (!view.isViewEmpty) { view.resetView() @@ -188,19 +199,6 @@ class AttendancePresenter @Inject constructor( return true } - private fun setBaseDateOnHolidays() { - flow { - val student = studentRepository.getCurrentStudent() - emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") - } - private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading attendance data started") @@ -211,11 +209,13 @@ class AttendancePresenter @Inject constructor( isParent = student.isParent val semester = semesterRepository.getCurrentSemester(student) + + checkInitialAndCurrentDate(student, semester) attendanceRepository.getAttendance( student = student, semester = semester, - start = currentDate, - end = currentDate, + start = currentDate ?: now(), + end = currentDate ?: now(), forceRefresh = forceRefresh ) } @@ -231,6 +231,8 @@ class AttendancePresenter @Inject constructor( }.sortedBy { item -> item.number } } .onResourceData { + isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it) + view?.run { enableSwipe(true) showProgress(false) @@ -238,6 +240,7 @@ class AttendancePresenter @Inject constructor( showEmpty(it.isEmpty()) showContent(it.isNotEmpty()) updateData(it) + reloadNavigation() } } .onResourceIntermediate { view?.showRefresh(true) } @@ -263,6 +266,43 @@ class AttendancePresenter @Inject constructor( .launch() } + private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { + if (initialDate == null) { + val lessons = attendanceRepository.getAttendance( + student = student, + semester = semester, + start = now().monday, + end = now().sunday, + forceRefresh = false, + ).toFirstResult().dataOrNull.orEmpty() + isWeekendHasLessons = isWeekendHasLessons(lessons) + initialDate = getInitialDate(semester) + } + + if (currentDate == null) { + currentDate = initialDate + } + } + + private fun isWeekendHasLessons( + lessons: List, + ): Boolean = lessons.any { + it.date.dayOfWeek in listOf( + DayOfWeek.SATURDAY, + DayOfWeek.SUNDAY, + ) + } + + private fun getInitialDate(semester: Semester): LocalDate { + val now = now() + + return when { + now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear) + isWeekendHasLessons -> now + else -> now.previousOrSameSchoolDay + } + } + private fun excuseAbsence(reason: String?, toExcuseList: List) { resourceFlow { val student = studentRepository.getCurrentStudent() @@ -311,7 +351,7 @@ class AttendancePresenter @Inject constructor( private fun reloadView(date: LocalDate) { currentDate = date - Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}") + Timber.i("Reload attendance view with the date ${currentDate?.toFormattedString()}") view?.apply { showProgress(true) enableSwipe(false) @@ -326,10 +366,13 @@ class AttendancePresenter @Inject constructor( @SuppressLint("DefaultLocale") private fun reloadNavigation() { + val currentDate = currentDate ?: return + view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays) updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) + showNavigation(true) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index b0123065a..2629c217e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -40,6 +40,8 @@ interface AttendanceView : BaseView { fun showContent(show: Boolean) + fun showNavigation(show: Boolean) + fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) 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 ecf084c68..ae451ae15 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 @@ -386,7 +386,7 @@ class DashboardPresenter @Inject constructor( private fun loadLessons(student: Student, forceRefresh: Boolean) { flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) - val date = LocalDate.now().nextOrSameSchoolDay + val date = LocalDate.now() timetableRepository.getTimetable( student = student, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index ebc16239f..0e6459110 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -9,6 +9,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import androidx.core.os.bundleOf import androidx.core.text.parseAsHtml +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -160,6 +161,10 @@ class TimetableFragment : BaseFragment(R.layout.fragme binding.timetableRecycler.visibility = if (show) VISIBLE else GONE } + override fun showNavigation(show: Boolean) { + binding.timetableNavContainer.isVisible = true + } + override fun showPreButton(show: Boolean) { binding.timetablePreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE } @@ -193,7 +198,9 @@ class TimetableFragment : BaseFragment(R.layout.fragme override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + presenter.currentDate?.toEpochDay()?.let { + outState.putLong(SAVED_DATE_KEY, it) + } } override fun onDestroyView() { 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 0f8395ded..f99970482 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,5 +1,8 @@ package io.github.wulkanowy.ui.modules.timetable +import io.github.wulkanowy.data.dataOrNull +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 @@ -15,6 +18,8 @@ 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.data.toFirstResult +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper @@ -24,15 +29,16 @@ 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.monday import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.sunday 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 kotlinx.coroutines.flow.firstOrNull import timber.log.Timber +import java.time.DayOfWeek import java.time.Instant import java.time.LocalDate import java.time.LocalDate.now @@ -51,9 +57,10 @@ class TimetablePresenter @Inject constructor( private val analytics: AnalyticsHelper, ) : BasePresenter(errorHandler, studentRepository) { - private var baseDate: LocalDate = now().nextOrSameSchoolDay + private var initialDate: LocalDate? = null + private var isWeekendHasLessons: Boolean = false - lateinit var currentDate: LocalDate + var currentDate: LocalDate? = null private set private lateinit var lastError: Throwable @@ -65,23 +72,30 @@ class TimetablePresenter @Inject constructor( view.initView() Timber.i("Timetable was initialized") errorHandler.showErrorMessage = ::showErrorViewOnError - reloadView(ofEpochDay(date ?: baseDate.toEpochDay())) + currentDate = date?.let(::ofEpochDay) loadData() - if (currentDate.isHolidays) setBaseDateOnHolidays() } fun onPreviousDay() { - reloadView(currentDate.previousSchoolDay) + val date = if (isWeekendHasLessons) { + currentDate?.minusDays(1) + } else currentDate?.previousSchoolDay + + reloadView(date ?: return) loadData() } fun onNextDay() { - reloadView(currentDate.nextSchoolDay) + val date = if (isWeekendHasLessons) { + currentDate?.plusDays(1) + } else currentDate?.nextSchoolDay + + reloadView(date ?: return) loadData() } fun onPickDate() { - view?.showDatePickerDialog(currentDate) + view?.showDatePickerDialog(currentDate ?: return) } fun onDateSet(year: Int, month: Int, day: Int) { @@ -110,10 +124,8 @@ class TimetablePresenter @Inject constructor( Timber.i("Timetable view is reselected") view?.let { view -> if (view.currentStackSize == 1) { - baseDate = now().nextOrSameSchoolDay - - if (currentDate != baseDate) { - reloadView(baseDate) + if (currentDate != initialDate) { + reloadView(initialDate ?: return) loadData() } else if (!view.isViewEmpty) { view.resetView() @@ -134,34 +146,25 @@ class TimetablePresenter @Inject constructor( return true } - private fun setBaseDateOnHolidays() { - flow { - val student = studentRepository.getCurrentStudent() - emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") - } - private fun loadData(forceRefresh: Boolean = false) { flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) + + checkInitialAndCurrentDate(student, semester) timetableRepository.getTimetable( student = student, semester = semester, - start = currentDate, - end = currentDate, + start = currentDate ?: now(), + end = currentDate ?: now(), forceRefresh = forceRefresh, timetableType = TimetableRepository.TimetableType.NORMAL ) } .logResourceStatus("load timetable data") .onResourceData { + isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons) + view?.run { enableSwipe(true) showProgress(false) @@ -169,7 +172,8 @@ class TimetablePresenter @Inject constructor( showContent(it.lessons.isNotEmpty()) showEmpty(it.lessons.isEmpty()) updateData(it.lessons) - setDayHeaderMessage(it.headers.singleOrNull { header -> header.date == currentDate }?.content) + setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content) + reloadNavigation() } } .onResourceIntermediate { view?.showRefresh(true) } @@ -191,6 +195,44 @@ class TimetablePresenter @Inject constructor( .launch() } + private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { + if (initialDate == null) { + val lessons = timetableRepository.getTimetable( + student = student, + semester = semester, + start = now().monday, + end = now().sunday, + forceRefresh = false, + timetableType = TimetableRepository.TimetableType.NORMAL + ).toFirstResult().dataOrNull?.lessons.orEmpty() + isWeekendHasLessons = isWeekendHasLessons(lessons) + initialDate = getInitialDate(semester) + } + + if (currentDate == null) { + currentDate = initialDate + } + } + + private fun isWeekendHasLessons( + lessons: List, + ): Boolean = lessons.any { + it.date.dayOfWeek in listOf( + DayOfWeek.SATURDAY, + DayOfWeek.SUNDAY, + ) + } + + private fun getInitialDate(semester: Semester): LocalDate { + val now = now() + + return when { + now.isHolidays -> now.getLastSchoolDayIfHoliday(semester.schoolYear) + isWeekendHasLessons -> now + else -> now.nextOrSameSchoolDay + } + } + private fun updateData(lessons: List) { tickTimer?.cancel() @@ -285,7 +327,7 @@ class TimetablePresenter @Inject constructor( private fun reloadView(date: LocalDate) { currentDate = date - Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}") + Timber.i("Reload timetable view with the date ${currentDate?.toFormattedString()}") view?.apply { showProgress(true) enableSwipe(false) @@ -298,10 +340,13 @@ class TimetablePresenter @Inject constructor( } private fun reloadNavigation() { + val currentDate = currentDate ?: return + view?.apply { showPreButton(!currentDate.minusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays) updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalise()) + showNavigation(true) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index 8cfb26204..40190d51f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -36,6 +36,8 @@ interface TimetableView : BaseView { fun showContent(show: Boolean) + fun showNavigation(show: Boolean) + fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml index 14331120c..078daf610 100644 --- a/app/src/main/res/layout/fragment_attendance.xml +++ b/app/src/main/res/layout/fragment_attendance.xml @@ -128,7 +128,9 @@ android:layout_gravity="bottom" android:gravity="center" android:orientation="horizontal" - tools:ignore="UnusedAttribute"> + android:visibility="gone" + tools:ignore="UnusedAttribute" + tools:visibility="visible"> + android:visibility="gone" + tools:ignore="UnusedAttribute" + tools:visibility="visible"> Date: Mon, 23 Oct 2023 16:47:03 +0000 Subject: [PATCH 306/545] Bump androidx.recyclerview:recyclerview from 1.3.1 to 1.3.2 (#2332) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a591d2b2e..2d86ff376 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -207,7 +207,7 @@ dependencies { implementation "androidx.annotation:annotation:1.7.0" implementation "androidx.preference:preference-ktx:1.2.1" - implementation "androidx.recyclerview:recyclerview:1.3.1" + implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.viewpager2:viewpager2:1.1.0-beta02" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" From 0e1c20a9527bfa7d1c82d7d851c5d1753b1e8400 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:48:23 +0000 Subject: [PATCH 307/545] Bump room from 2.5.2 to 2.6.0 (#2329) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2d86ff376..97b526220 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -185,7 +185,7 @@ huaweiPublish { ext { work_manager = "2.8.1" android_hilt = "1.0.0" - room = "2.5.2" + room = "2.6.0" chucker = "3.5.2" mockk = "1.13.8" coroutines = "1.7.3" From 3d76d41b55278ba21e6d7297b75695ad223f09f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:49:58 +0000 Subject: [PATCH 308/545] Bump com.squareup.okhttp3:logging-interceptor from 4.11.0 to 4.12.0 (#2330) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 97b526220..47ef3cde0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -236,7 +236,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" - implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" + implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.jakewharton.timber:timber:5.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1" From a4a191700e5b449efa177125db3caad07421ad8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:28:38 +0000 Subject: [PATCH 309/545] Bump com.google.firebase:firebase-bom from 32.3.1 to 32.4.0 (#2331) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 47ef3cde0..066b3736d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,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.3.1') + playImplementation platform('com.google.firebase:firebase-bom:32.4.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From fcea2218b53e06f19a31c5a2f777edb676fa529c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 23 Oct 2023 19:56:46 +0200 Subject: [PATCH 310/545] Version 2.2.2 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 066b3736d..42a1f8f74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 133 - versionName "2.2.1" + versionCode 134 + versionName "2.2.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.1' + implementation 'io.github.wulkanowy:sdk:2.2.2' 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 dc78c1e3d..2d8d4ce7f 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,7 @@ -Wersja 2.2.1 +Wersja 2.2.2 -– dokonaliśmy kilka poprawek na ekranie logowania -– naprawiliśmy przypadek z błędnym wyświetlaniem starej klasy ucznia po zalogowaniu się na konto z nowej klasy +— dodaliśmy możliwość łatwego wejścia w sobotę i niedziele w planie lekcji przy użyciu strzałek +— poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym +— poprawiliśmy sortowanie nauczycieli w widoku Szkoła i nauczyciele Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 762d4b13931768247bf5b29768dbbaef13c4f7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 26 Oct 2023 10:06:54 +0200 Subject: [PATCH 311/545] Timetable timers fixes (#2333) --- .../wulkanowy/data/repositories/PreferencesRepository.kt | 6 ------ .../wulkanowy/ui/modules/timetable/TimetableAdapter.kt | 2 +- .../wulkanowy/ui/modules/timetable/TimetablePresenter.kt | 8 ++++++-- 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-it-rIT/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 - app/src/main/res/values/preferences_defaults.xml | 1 - app/src/main/res/values/preferences_keys.xml | 1 - app/src/main/res/values/strings.xml | 1 - app/src/main/res/xml/scheme_preferences_appearance.xml | 5 ----- 16 files changed, 7 insertions(+), 26 deletions(-) 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 85c74072e..495415f9f 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 @@ -194,12 +194,6 @@ class PreferencesRepository @Inject constructor( ) ) - val showTimetableTimers: Boolean - get() = getBoolean( - R.string.pref_key_timetable_show_timers, - R.bool.pref_default_timetable_show_timers - ) - val showTimetableGaps: TimetableGapsMode get() = TimetableGapsMode.getByValue( getString( 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 1201937c0..a957ef798 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 @@ -325,7 +325,7 @@ class TimetableAdapter @Inject constructor() : override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? { return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) { - if (oldItem.lesson == newItem.lesson && oldItem.timeLeft != newItem.timeLeft) { + if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) { "time_left" } else super.getChangePayload(oldItem, newItem) } else super.getChangePayload(oldItem, newItem) 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 f99970482..6b442d1c2 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,5 +1,7 @@ package io.github.wulkanowy.ui.modules.timetable +import android.os.Handler +import android.os.Looper import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -236,11 +238,13 @@ class TimetablePresenter @Inject constructor( private fun updateData(lessons: List) { tickTimer?.cancel() - if (!prefRepository.showTimetableTimers) { + if (currentDate != now()) { view?.updateData(createItems(lessons)) } else { tickTimer = timer(period = 2_000) { - view?.updateData(createItems(lessons)) + Handler(Looper.getMainLooper()).post { + view?.updateData(createItems(lessons)) + } } } } diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1749548e1..3af494fa4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -713,7 +713,6 @@ Zobrazit přítomnost Motiv Rozvíjení známek - Označit aktuální lekci Zobrazit skupiny vedle předmětů Zobrazit prázdné dlaždice, kde není žádná lekce Zobrazit seznam grafů v známkách třídy diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 500fba1f3..a89b83769 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -623,7 +623,6 @@ Show presence Theme Grades expanding - Mark current lesson Show groups next to subjects Show empty tiles where there\'s no lesson Show chart list in class grades diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d8cc92984..4c9aa2d28 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -623,7 +623,6 @@ Anwesendheit zeigen Thema Steigende Sorten - Aktuelle Lektion markieren Gruppen neben Schulfächen anzeigen Show empty tiles where there\'s no lesson Liste der Diagramme in Klassenbewertungen anzeigen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 500fba1f3..a89b83769 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -623,7 +623,6 @@ Show presence Theme Grades expanding - Mark current lesson Show groups next to subjects Show empty tiles where there\'s no lesson Show chart list in class grades diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 500fba1f3..a89b83769 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -623,7 +623,6 @@ Show presence Theme Grades expanding - Mark current lesson Show groups next to subjects Show empty tiles where there\'s no lesson Show chart list in class grades diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9375aeb39..31b9ce32c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -713,7 +713,6 @@ Pokazuj obecność Motyw Rozwijanie ocen - Oznaczaj bieżącą lekcję Pokazuj grupę obok przedmiotu Pokazuj puste kafelki gdzie nie ma lekcji Pokazuj listę wykresów w ocenach klasy diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a6e45d445..89a94a59d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -713,7 +713,6 @@ Показывать присутствия Тема Разворачивание оценок - Отметить текущий урок Показать группы рядом с темами Show empty tiles where there\'s no lesson Показывать диаграммы в оценках класса diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index ed06e023e..3cde8152c 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -713,7 +713,6 @@ Zobraziť prítomnosť Motív Rozvijanie známok - Označiť aktuálne lekciu Zobraziť skupiny vedľa predmetov Zobraziť prázdne dlaždice, kde nie je žiadne lekcie Zobraziť zoznam grafov v známkach triedy diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 6a79263f7..20a917aa4 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -713,7 +713,6 @@ Показувати присутність Тема Розгортання оцінок - Позначити поточний урок Показувати групи поруч з темами Показувати порожні плитки там, де немає уроків Показувати діаграми в оцінках класу diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 8d69f25c8..8e6fc7d66 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -22,7 +22,6 @@ false no alphabetic - false between false false diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index c48381e8c..5afffb649 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -27,7 +27,6 @@ grade_sorting_mode show_whole_class_plan show_groups_in_plan - timetable_show_timers timetable_show_gaps subjects_without_grades optional_arithmetic_average diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ba9c9970..5cad09d0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -701,7 +701,6 @@ Show presence Theme Grades expanding - Mark current lesson Show groups next to subjects Show empty tiles where there\'s no lesson Show chart list in class grades diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index 7177d396c..9c02a4910 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -90,11 +90,6 @@ android:layout_height="wrap_content" app:iconSpaceReserved="false" app:title="@string/pref_timetable_appearance_view"> - Date: Thu, 26 Oct 2023 14:01:45 +0200 Subject: [PATCH 312/545] New Crowdin updates (#2334) From eef3464d0bc79233a996d711903a8159079bc7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 26 Oct 2023 18:31:51 +0200 Subject: [PATCH 313/545] Version 2.2.3 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 42a1f8f74..afa4ac160 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 134 - versionName "2.2.2" + versionCode 135 + versionName "2.2.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.2' + implementation 'io.github.wulkanowy:sdk:2.2.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 2d8d4ce7f..dfc045462 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,6 @@ -Wersja 2.2.2 +Wersja 2.2.3 -— dodaliśmy możliwość łatwego wejścia w sobotę i niedziele w planie lekcji przy użyciu strzałek +— ułatwiliśmy przełączenie dnia na weekend w planie lekcji przy użyciu strzałek — poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym — poprawiliśmy sortowanie nauczycieli w widoku Szkoła i nauczyciele From 1dbaa8bfdc327b11ceedaa24fd3f2449355247d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 27 Oct 2023 14:09:42 +0200 Subject: [PATCH 314/545] Migrate to separate app-update and review artifacts from play:core (#2336) --- app/build.gradle | 4 +- .../wulkanowy/utils/InAppUpdateHelper.kt | 13 ++++ .../io/github/wulkanowy/utils/UpdateHelper.kt | 17 ------ .../wulkanowy/utils/InAppUpdateHelper.kt | 13 ++++ .../io/github/wulkanowy/utils/UpdateHelper.kt | 17 ------ .../ui/modules/login/LoginActivity.kt | 18 ++---- .../wulkanowy/ui/modules/main/MainActivity.kt | 15 ++--- .../{UpdateHelper.kt => InAppUpdateHelper.kt} | 59 +++++++++++-------- 8 files changed, 71 insertions(+), 85 deletions(-) create mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt delete mode 100644 app/src/fdroid/java/io/github/wulkanowy/utils/UpdateHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt delete mode 100644 app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt rename app/src/play/java/io/github/wulkanowy/utils/{UpdateHelper.kt => InAppUpdateHelper.kt} (76%) diff --git a/app/build.gradle b/app/build.gradle index afa4ac160..88becb9a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -253,10 +253,10 @@ dependencies { playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' 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.4.0' playImplementation "com.google.android.play:integrity:1.2.0" + playImplementation 'com.google.android.play:app-update-ktx:2.1.0' + playImplementation 'com.google.android.play:review-ktx:2.0.1' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt new file mode 100644 index 000000000..51b22ec74 --- /dev/null +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.utils + +import android.view.View +import javax.inject.Inject + +class InAppUpdateHelper @Inject constructor() { + + lateinit var messageContainer: View + + fun checkAndInstallUpdates() {} + + fun onResume() {} +} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/UpdateHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/UpdateHelper.kt deleted file mode 100644 index 3abab9629..000000000 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/UpdateHelper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.wulkanowy.utils - -import android.app.Activity -import android.view.View -import javax.inject.Inject - -@Suppress("UNUSED_PARAMETER") -class UpdateHelper @Inject constructor() { - - lateinit var messageContainer: View - - fun checkAndInstallUpdates(activity: Activity) {} - - fun onActivityResult(requestCode: Int, resultCode: Int) {} - - fun onResume(activity: Activity) {} -} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt new file mode 100644 index 000000000..51b22ec74 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.utils + +import android.view.View +import javax.inject.Inject + +class InAppUpdateHelper @Inject constructor() { + + lateinit var messageContainer: View + + fun checkAndInstallUpdates() {} + + fun onResume() {} +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt deleted file mode 100644 index 3abab9629..000000000 --- a/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.wulkanowy.utils - -import android.app.Activity -import android.view.View -import javax.inject.Inject - -@Suppress("UNUSED_PARAMETER") -class UpdateHelper @Inject constructor() { - - lateinit var messageContainer: View - - fun checkAndInstallUpdates(activity: Activity) {} - - fun onActivityResult(requestCode: Int, resultCode: Int) {} - - fun onResume(activity: Activity) {} -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt index c17c92efd..88f295788 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginActivity.kt @@ -23,7 +23,7 @@ import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.UpdateHelper +import io.github.wulkanowy.utils.InAppUpdateHelper import javax.inject.Inject @AndroidEntryPoint @@ -33,7 +33,7 @@ class LoginActivity : BaseActivity(), Logi override lateinit var presenter: LoginPresenter @Inject - lateinit var updateHelper: UpdateHelper + lateinit var inAppUpdateHelper: InAppUpdateHelper @Inject lateinit var appInfo: AppInfo @@ -47,10 +47,10 @@ class LoginActivity : BaseActivity(), Logi setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root) setSupportActionBar(binding.loginToolbar) messageContainer = binding.loginContainer - updateHelper.messageContainer = binding.loginContainer + inAppUpdateHelper.messageContainer = binding.loginContainer presenter.onAttachView(this) - updateHelper.checkAndInstallUpdates(this) + inAppUpdateHelper.checkAndInstallUpdates() if (savedInstanceState == null) { openFragment(LoginFormFragment.newInstance(), clearBackStack = true) @@ -117,14 +117,6 @@ class LoginActivity : BaseActivity(), Logi override fun onResume() { super.onResume() - updateHelper.onResume(this) - } - - //https://developer.android.com/guide/playcore/in-app-updates#status_callback - @Deprecated("Deprecated in Java") - @Suppress("DEPRECATION") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - updateHelper.onActivityResult(requestCode, resultCode) + inAppUpdateHelper.onResume() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 178d6e94b..25ab73bca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -45,7 +45,7 @@ class MainActivity : BaseActivity(), MainVie lateinit var analytics: AnalyticsHelper @Inject - lateinit var updateHelper: UpdateHelper + lateinit var inAppUpdateHelper: InAppUpdateHelper @Inject lateinit var inAppReviewHelper: InAppReviewHelper @@ -100,7 +100,7 @@ class MainActivity : BaseActivity(), MainVie this.savedInstanceState = savedInstanceState messageContainer = binding.mainMessageContainer messageAnchor = binding.mainMessageContainer - updateHelper.messageContainer = binding.mainFragmentContainer + inAppUpdateHelper.messageContainer = binding.mainFragmentContainer onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) { presenter.onBackPressed() } @@ -109,19 +109,12 @@ class MainActivity : BaseActivity(), MainVie ?.takeIf { savedInstanceState == null } presenter.onAttachView(this, destination) - updateHelper.checkAndInstallUpdates(this) + inAppUpdateHelper.checkAndInstallUpdates() } override fun onResume() { super.onResume() - updateHelper.onResume(this) - } - - //https://developer.android.com/guide/playcore/in-app-updates#status_callback - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - updateHelper.onActivityResult(requestCode, resultCode) + inAppUpdateHelper.onResume() } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt similarity index 76% rename from app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt rename to app/src/play/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt index 6772237e2..d89dfd5f8 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/UpdateHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/InAppUpdateHelper.kt @@ -3,12 +3,15 @@ package io.github.wulkanowy.utils import android.app.Activity import android.app.Activity.RESULT_OK import android.content.Context -import android.content.IntentSender import android.view.View import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import com.google.android.material.snackbar.Snackbar import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.InstallStateUpdatedListener import com.google.android.play.core.install.model.AppUpdateType.FLEXIBLE import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE @@ -20,15 +23,16 @@ import com.google.android.play.core.ktx.isFlexibleUpdateAllowed import com.google.android.play.core.ktx.isImmediateUpdateAllowed import com.google.android.play.core.ktx.updatePriority import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ActivityScoped import io.github.wulkanowy.R import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class UpdateHelper @Inject constructor( +@ActivityScoped +class InAppUpdateHelper @Inject constructor( @ApplicationContext private val context: Context, private val analyticsHelper: AnalyticsHelper, + activity: Activity ) { lateinit var messageContainer: View @@ -39,6 +43,7 @@ class UpdateHelper @Inject constructor( when (state.installStatus()) { PENDING -> Toast.makeText(context, R.string.update_download_started, Toast.LENGTH_SHORT) .show() + DOWNLOADED -> popupSnackBarForCompleteUpdate() else -> Timber.d("Update state: ${state.installStatus()}") } @@ -70,45 +75,55 @@ class UpdateHelper @Inject constructor( return updateAvailability() == UPDATE_AVAILABLE && isFlexibleUpdateAllowed && isUpdatePriorityAllowUpdate } - fun checkAndInstallUpdates(activity: Activity) { + private val activityResultLauncher = (activity as AppCompatActivity).registerForActivityResult( + ActivityResultContracts.StartIntentSenderForResult(), + ::onActivityResult + ) + + fun checkAndInstallUpdates() { Timber.d("Checking for updates...") appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo -> when { appUpdateInfo.isImmediateUpdateAvailable -> { - startUpdate(activity, appUpdateInfo, IMMEDIATE) + startUpdate(appUpdateInfo, IMMEDIATE) } + appUpdateInfo.isFlexibleUpdateAvailable -> { appUpdateManager.registerListener(flexibleUpdateListener) - startUpdate(activity, appUpdateInfo, FLEXIBLE) + startUpdate(appUpdateInfo, FLEXIBLE) } + else -> Timber.d("No update available") } } } - private fun startUpdate(activity: Activity, appUpdateInfo: AppUpdateInfo, updateType: Int) { + private fun startUpdate(appUpdateInfo: AppUpdateInfo, updateType: Int) { Timber.d("Start update ($updateType): $appUpdateInfo") + try { appUpdateManager.startUpdateFlowForResult( - appUpdateInfo, updateType, activity, IN_APP_UPDATE_REQUEST_CODE + appUpdateInfo, + activityResultLauncher, + AppUpdateOptions.defaultOptions(updateType) ) - } catch (e: IntentSender.SendIntentException) { - Timber.i("Update failed! Duplicated PendingIntent") + } catch (e: Exception) { + Timber.e(e, "Update failed!") } } - fun onActivityResult(requestCode: Int, resultCode: Int) { - if (requestCode == IN_APP_UPDATE_REQUEST_CODE) { - if (resultCode != RESULT_OK) { - Timber.i("Update failed! Result code: $resultCode") - Toast.makeText(context, R.string.update_failed, Toast.LENGTH_LONG).show() - } + private fun onActivityResult(activityResult: ActivityResult) { + val resultCode = activityResult.resultCode - analyticsHelper.logEvent("inapp_update", "code" to resultCode) + if (resultCode != RESULT_OK) { + Timber.i("Update failed! Result code: $resultCode") + Toast.makeText(context, R.string.update_failed, Toast.LENGTH_LONG).show() } + + analyticsHelper.logEvent("inapp_update", "code" to resultCode) } - fun onResume(activity: Activity) { + fun onResume() { appUpdateManager.appUpdateInfo.addOnSuccessListener { info -> Timber.d("InAppUpdate.onResume() listener: $info") @@ -116,7 +131,6 @@ class UpdateHelper @Inject constructor( DOWNLOADED == info.installStatus() -> popupSnackBarForCompleteUpdate() DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS == info.updateAvailability() -> { startUpdate( - activity = activity, appUpdateInfo = info, updateType = if (info.isImmediateUpdateAvailable) IMMEDIATE else FLEXIBLE ) @@ -139,9 +153,4 @@ class UpdateHelper @Inject constructor( show() } } - - private companion object { - - private const val IN_APP_UPDATE_REQUEST_CODE = 1721 - } } From 124b6dfd79bcb5a4898219a7e7db5f5622437c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 27 Oct 2023 14:49:41 +0200 Subject: [PATCH 315/545] Version 2.2.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 88becb9a2..0fcedc68e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 135 - versionName "2.2.3" + versionCode 136 + versionName "2.2.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,7 +162,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.01d - updatePriority = 3 + updatePriority = 0 enabled.set(false) } @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.3' + implementation 'io.github.wulkanowy:sdk:2.2.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 dfc045462..907221d79 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.2.3 +Wersja 2.2.4 — ułatwiliśmy przełączenie dnia na weekend w planie lekcji przy użyciu strzałek — poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym From 3fd2683df7127ac49292515e56c844998a0e0075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 1 Nov 2023 16:47:39 +0100 Subject: [PATCH 316/545] Update dependencies and remove deprecations (#2340) --- app/build.gradle | 8 +-- .../data/db/migrations/Migration10.kt | 4 +- .../data/db/migrations/Migration11.kt | 14 +++-- .../data/db/migrations/Migration12.kt | 46 +++++++------- .../data/db/migrations/Migration13.kt | 50 ++++++++------- .../data/db/migrations/Migration14.kt | 7 ++- .../data/db/migrations/Migration15.kt | 8 ++- .../data/db/migrations/Migration16.kt | 8 ++- .../data/db/migrations/Migration17.kt | 18 +++--- .../data/db/migrations/Migration18.kt | 5 +- .../data/db/migrations/Migration19.kt | 62 +++++++++++-------- .../data/db/migrations/Migration2.kt | 8 ++- .../data/db/migrations/Migration20.kt | 20 +++--- .../data/db/migrations/Migration21.kt | 10 +-- .../data/db/migrations/Migration22.kt | 4 +- .../data/db/migrations/Migration23.kt | 10 +-- .../data/db/migrations/Migration24.kt | 10 +-- .../data/db/migrations/Migration25.kt | 6 +- .../data/db/migrations/Migration26.kt | 10 +-- .../data/db/migrations/Migration27.kt | 21 ++++--- .../data/db/migrations/Migration28.kt | 5 +- .../data/db/migrations/Migration29.kt | 13 ++-- .../data/db/migrations/Migration3.kt | 5 +- .../data/db/migrations/Migration30.kt | 8 ++- .../data/db/migrations/Migration31.kt | 4 +- .../data/db/migrations/Migration32.kt | 4 +- .../data/db/migrations/Migration33.kt | 6 +- .../data/db/migrations/Migration34.kt | 6 +- .../data/db/migrations/Migration35.kt | 8 +-- .../data/db/migrations/Migration36.kt | 6 +- .../data/db/migrations/Migration37.kt | 4 +- .../data/db/migrations/Migration38.kt | 8 ++- .../data/db/migrations/Migration39.kt | 8 +-- .../data/db/migrations/Migration4.kt | 7 ++- .../data/db/migrations/Migration40.kt | 6 +- .../data/db/migrations/Migration41.kt | 6 +- .../data/db/migrations/Migration42.kt | 6 +- .../data/db/migrations/Migration43.kt | 6 +- .../data/db/migrations/Migration44.kt | 4 +- .../data/db/migrations/Migration46.kt | 50 +++++++-------- .../data/db/migrations/Migration49.kt | 6 +- .../data/db/migrations/Migration5.kt | 18 ++++-- .../data/db/migrations/Migration50.kt | 6 +- .../data/db/migrations/Migration51.kt | 40 ++++++------ .../data/db/migrations/Migration53.kt | 18 +++--- .../data/db/migrations/Migration54.kt | 18 +++--- .../data/db/migrations/Migration6.kt | 20 +++--- .../data/db/migrations/Migration7.kt | 8 ++- .../data/db/migrations/Migration8.kt | 8 +-- .../data/db/migrations/Migration9.kt | 7 ++- build.gradle | 4 -- 51 files changed, 353 insertions(+), 299 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0fcedc68e..3a90d3fb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,13 +242,13 @@ dependencies { implementation "at.favre.lib:slf4j-timber:1.0.1" implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" - implementation "io.coil-kt:coil:2.4.0" + implementation 'io.coil-kt:coil:2.5.0' implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.9.1' - implementation 'org.apache.commons:commons-text:1.10.0' + implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.4.0') + playImplementation platform('com.google.firebase:firebase-bom:32.4.1') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' @@ -272,7 +272,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.10.3' + testImplementation 'org.robolectric:robolectric:4.11' 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/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt index c26a02d1f..0e7e14097 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration10.kt @@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration10 : Migration(9, 10) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt index 6d129bca0..342e2e2e3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration11.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration11 : Migration(10, 11) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Grades_temp ( id INTEGER PRIMARY KEY NOT NULL, is_read INTEGER NOT NULL, @@ -26,9 +27,10 @@ class Migration11 : Migration(10, 11) { date INTEGER NOT NULL, teacher TEXT NOT NULL ) - """) - database.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades") - database.execSQL("DROP TABLE Grades") - database.execSQL("ALTER TABLE Grades_temp RENAME TO Grades") + """ + ) + db.execSQL("INSERT INTO Grades_temp SELECT * FROM Grades") + db.execSQL("DROP TABLE Grades") + db.execSQL("ALTER TABLE Grades_temp RENAME TO Grades") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt index c827b82ba..6cc726953 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration12.kt @@ -5,16 +5,17 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration12 : Migration(11, 12) { - override fun migrate(database: SupportSQLiteDatabase) { - createTempStudentsTable(database) - replaceStudentTable(database) - updateStudentsWithClassId(database, getStudentsIds(database)) - removeStudentsWithNoClassId(database) - ensureThereIsOnlyOneCurrentStudent(database) + override fun migrate(db: SupportSQLiteDatabase) { + createTempStudentsTable(db) + replaceStudentTable(db) + updateStudentsWithClassId(db, getStudentsIds(db)) + removeStudentsWithNoClassId(db) + ensureThereIsOnlyOneCurrentStudent(db) } - private fun createTempStudentsTable(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun createTempStudentsTable(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Students_tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, endpoint TEXT NOT NULL, @@ -30,15 +31,16 @@ class Migration12 : Migration(11, 12) { registration_date INTEGER NOT NULL, class_id INTEGER NOT NULL ) - """) - database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)") + """ + ) + db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students_tmp (email, symbol, student_id, school_id, class_id)") } - private fun replaceStudentTable(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") - database.execSQL("INSERT INTO Students_tmp SELECT * FROM Students") - database.execSQL("DROP TABLE Students") - database.execSQL("ALTER TABLE Students_tmp RENAME TO Students") + private fun replaceStudentTable(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("INSERT INTO Students_tmp SELECT * FROM Students") + db.execSQL("DROP TABLE Students") + db.execSQL("ALTER TABLE Students_tmp RENAME TO Students") } private fun getStudentsIds(database: SupportSQLiteDatabase): List { @@ -54,18 +56,18 @@ class Migration12 : Migration(11, 12) { return students } - private fun updateStudentsWithClassId(database: SupportSQLiteDatabase, students: List) { + private fun updateStudentsWithClassId(db: SupportSQLiteDatabase, students: List) { students.forEach { - database.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it") + db.execSQL("UPDATE Students SET class_id = IFNULL((SELECT class_id FROM Semesters WHERE student_id = $it), 0) WHERE student_id = $it") } } - private fun removeStudentsWithNoClassId(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Students WHERE class_id = 0") + private fun removeStudentsWithNoClassId(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Students WHERE class_id = 0") } - private fun ensureThereIsOnlyOneCurrentStudent(database: SupportSQLiteDatabase) { - database.execSQL("UPDATE Students SET is_current = 0") - database.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)") + private fun ensureThereIsOnlyOneCurrentStudent(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE Students SET is_current = 0") + db.execSQL("UPDATE Students SET is_current = 1 WHERE id = (SELECT MAX(id) FROM Students)") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt index 36de1e837..c5030232b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration13.kt @@ -5,27 +5,30 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration13 : Migration(12, 13) { - override fun migrate(database: SupportSQLiteDatabase) { - addClassNameToStudents(database, getStudentsIds(database)) - updateSemestersTable(database) - markAtLeastAndOnlyOneSemesterAtCurrent(database, getStudentsAndClassIds(database)) - clearMessagesTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + addClassNameToStudents(db, getStudentsIds(db)) + updateSemestersTable(db) + markAtLeastAndOnlyOneSemesterAtCurrent(db, getStudentsAndClassIds(db)) + clearMessagesTable(db) } - private fun addClassNameToStudents(database: SupportSQLiteDatabase, students: List>) { - database.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL") + private fun addClassNameToStudents( + db: SupportSQLiteDatabase, + students: List> + ) { + db.execSQL("ALTER TABLE Students ADD COLUMN class_name TEXT DEFAULT \"\" NOT NULL") students.forEach { (id, name) -> val schoolName = name.substringAfter(" - ") val className = name.substringBefore(" - ", "").replace("Klasa ", "") - database.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'") - database.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'") + db.execSQL("UPDATE Students SET class_name = '$className' WHERE id = '$id'") + db.execSQL("UPDATE Students SET school_name = '$schoolName' WHERE id = '$id'") } } - private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList> { + private fun getStudentsIds(db: SupportSQLiteDatabase): MutableList> { val students = mutableListOf>() - database.query("SELECT id, school_name FROM Students").use { + db.query("SELECT id, school_name FROM Students").use { if (it.moveToFirst()) { do { students.add(it.getInt(0) to it.getString(1)) @@ -36,15 +39,15 @@ class Migration13 : Migration(12, 13) { return students } - private fun updateSemestersTable(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL") + private fun updateSemestersTable(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Semesters ADD COLUMN school_year INTEGER DEFAULT 1970 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN start INTEGER DEFAULT 0 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN `end` INTEGER DEFAULT 0 NOT NULL") } - private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List> { + private fun getStudentsAndClassIds(db: SupportSQLiteDatabase): List> { val students = mutableListOf>() - database.query("SELECT student_id, class_id FROM Students").use { + db.query("SELECT student_id, class_id FROM Students").use { if (it.moveToFirst()) { do { students.add(it.getInt(0) to it.getInt(1)) @@ -55,14 +58,17 @@ class Migration13 : Migration(12, 13) { return students } - private fun markAtLeastAndOnlyOneSemesterAtCurrent(database: SupportSQLiteDatabase, students: List>) { + private fun markAtLeastAndOnlyOneSemesterAtCurrent( + db: SupportSQLiteDatabase, + students: List> + ) { students.forEach { (studentId, classId) -> - database.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'") - database.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)") + db.execSQL("UPDATE Semesters SET is_current = 0 WHERE student_id = '$studentId' AND class_id = '$classId'") + db.execSQL("UPDATE Semesters SET is_current = 1 WHERE id = (SELECT id FROM Semesters WHERE student_id = '$studentId' AND class_id = '$classId' ORDER BY semester_id DESC)") } } - private fun clearMessagesTable(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Messages") + private fun clearMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Messages") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt index 4dac0d306..793b4a9d2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration14.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration14 : Migration(13, 14) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS GradesSummary") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS GradesSummary") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesSummary ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, semester_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt index 5be49a95b..5ff44e9ca 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration15.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration15 : Migration(14, 15) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS MobileDevices ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -14,6 +15,7 @@ class Migration15 : Migration(14, 15) { name TEXT NOT NULL, date INTEGER NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt index 7f40c0f8d..8a8f5b8f2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration16.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration16 : Migration(15, 16) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Teachers ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -15,6 +16,7 @@ class Migration16 : Migration(15, 16) { name TEXT NOT NULL, short_name TEXT NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt index e2a2574db..cf3318ad4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration17.kt @@ -5,13 +5,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration17 : Migration(16, 17) { - override fun migrate(database: SupportSQLiteDatabase) { - createGradesPointsStatisticsTable(database) - truncateSemestersTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + createGradesPointsStatisticsTable(db) + truncateSemestersTable(db) } - private fun createGradesPointsStatisticsTable(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun createGradesPointsStatisticsTable(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesPointsStatistics( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -20,10 +21,11 @@ class Migration17 : Migration(16, 17) { others REAL NOT NULL, student REAL NOT NULL ) - """) + """ + ) } - private fun truncateSemestersTable(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Semesters") + private fun truncateSemestersTable(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Semesters") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt index 6c5e56c6a..713f8e724 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration18.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration18 : Migration(17, 18) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS School ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt index d38f1245a..021cdbb37 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration19.kt @@ -6,16 +6,17 @@ import io.github.wulkanowy.data.db.SharedPrefProvider class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migration(18, 19) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateMessages(database) - migrateGrades(database) - migrateStudents(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateMessages(db) + migrateGrades(db) + migrateStudents(db) migrateSharedPreferences() } - private fun migrateMessages(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Messages") - database.execSQL(""" + private fun migrateMessages(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, is_notified INTEGER NOT NULL, @@ -34,12 +35,14 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio read_by INTEGER NOT NULL, removed INTEGER NOT NULL ) - """) + """ + ) } - private fun migrateGrades(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Grades") - database.execSQL(""" + private fun migrateGrades(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Grades") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Grades ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, is_read INTEGER NOT NULL, @@ -59,11 +62,13 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio date INTEGER NOT NULL, teacher TEXT NOT NULL ) - """) + """ + ) } - private fun migrateStudents(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun migrateStudents(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Students_tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, scrapper_base_url TEXT NOT NULL, @@ -86,26 +91,29 @@ class Migration19(private val sharedPrefProvider: SharedPrefProvider) : Migratio is_current INTEGER NOT NULL, registration_date INTEGER NOT NULL ) - """) + """ + ) - database.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;") - database.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";") - database.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;") + db.execSQL("ALTER TABLE Students ADD COLUMN scrapperBaseUrl TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN apiBaseUrl TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN is_parent INT NOT NULL DEFAULT 0;") + db.execSQL("ALTER TABLE Students ADD COLUMN loginMode TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN certificateKey TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN privateKey TEXT NOT NULL DEFAULT \"\";") + db.execSQL("ALTER TABLE Students ADD COLUMN user_login_id INTEGER NOT NULL DEFAULT 0;") - database.execSQL(""" + db.execSQL( + """ INSERT INTO Students_tmp( id, scrapper_base_url, mobile_base_url, is_parent, login_type, login_mode, certificate_key, private_key, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date) SELECT id, endpoint, apiBaseUrl, is_parent, loginType, "SCRAPPER", certificateKey, privateKey, email, password, symbol, student_id, user_login_id, student_name, school_id, school_name, school_id, school_name, class_name, class_id, is_current, registration_date FROM Students - """) - database.execSQL("DROP TABLE Students") - database.execSQL("ALTER TABLE Students_tmp RENAME TO Students") - database.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)") + """ + ) + db.execSQL("DROP TABLE Students") + db.execSQL("ALTER TABLE Students_tmp RENAME TO Students") + db.execSQL("CREATE UNIQUE INDEX index_Students_email_symbol_student_id_school_id_class_id ON Students (email, symbol, student_id, school_id, class_id)") } private fun migrateSharedPreferences() { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt index c5a30991a..be8675092 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration2.kt @@ -5,14 +5,16 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration2 : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS LuckyNumbers ( id INTEGER PRIMARY KEY NOT NULL, is_notified INTEGER NOT NULL, student_id INTEGER NOT NULL, date INTEGER NOT NULL, lucky_number INTEGER NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt index 2fcfc183d..7ad43230b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration20.kt @@ -5,14 +5,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration20 : Migration(19, 20) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateTimetable(database) - truncateSubjects(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateTimetable(db) + truncateSubjects(db) } - private fun migrateTimetable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE Timetable") - database.execSQL(""" + private fun migrateTimetable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE Timetable") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS `Timetable` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, @@ -33,10 +34,11 @@ class Migration20 : Migration(19, 20) { `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL ) - """) + """ + ) } - private fun truncateSubjects(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Subjects") + private fun truncateSubjects(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt index bc0ff900c..60e044cdc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration21.kt @@ -5,11 +5,11 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration21 : Migration(20, 21) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL") - database.execSQL("DELETE FROM Semesters") + db.execSQL("DELETE FROM Semesters") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt index cf50a6c3e..ef525a49d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration22.kt @@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration22 : Migration(21, 22) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN school_short TEXT NOT NULL DEFAULT ''") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt index 22de94c3f..3650307a6 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration23.kt @@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration23 : Migration(22, 23) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''") - database.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Notes ADD COLUMN teacher_symbol TEXT NOT NULL DEFAULT ''") + db.execSQL("ALTER TABLE Notes ADD COLUMN category_type INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Notes ADD COLUMN is_points_show INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Notes ADD COLUMN points INTEGER NOT NULL DEFAULT 0") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt index 604ed4875..a3cd98197 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration24.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration24 : Migration(23, 24) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Messages ADD COLUMN has_attachments INTEGER NOT NULL DEFAULT 0") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS MessageAttachments ( real_id INTEGER NOT NULL, message_id INTEGER NOT NULL, @@ -16,6 +17,7 @@ class Migration24 : Migration(23, 24) { filename TEXT NOT NULL, PRIMARY KEY(real_id) ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt index 4749bac73..cb395d7e0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration25.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration25 : Migration(24, 25) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Homework ADD COLUMN is_done INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Homework ADD COLUMN attachments TEXT NOT NULL DEFAULT \"[]\"") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt index 7130d86d8..94746b456 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration26.kt @@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration26 : Migration(25, 26) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1") - database.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1") - database.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0") - database.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_predicted_grade_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN is_final_grade_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN predicted_grade_last_change INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE GradesSummary ADD COLUMN final_grade_last_change INTEGER NOT NULL DEFAULT 0") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt index 5c60beead..a7ba763df 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration27.kt @@ -5,24 +5,25 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration27 : Migration(26, 27) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN user_name TEXT NOT NULL DEFAULT \"\"") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN user_name TEXT NOT NULL DEFAULT \"\"") - val students = getStudentsIdsAndNames(database) - val units = getReportingUnits(database) + val students = getStudentsIdsAndNames(db) + val units = getReportingUnits(db) students.forEach { (id, userLoginId, studentName) -> - val userNameFromUnits = units.singleOrNull { (senderId, _) -> senderId == userLoginId }?.second + val userNameFromUnits = + units.singleOrNull { (senderId, _) -> senderId == userLoginId }?.second val normalizedStudentName = studentName.split(" ").asReversed().joinToString(" ") val userName = userNameFromUnits ?: normalizedStudentName - database.execSQL("UPDATE Students SET user_name = '$userName' WHERE id = '$id'") + db.execSQL("UPDATE Students SET user_name = '$userName' WHERE id = '$id'") } } - private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList> { + private fun getStudentsIdsAndNames(db: SupportSQLiteDatabase): MutableList> { val students = mutableListOf>() - database.query("SELECT id, user_login_id, student_name FROM Students").use { + db.query("SELECT id, user_login_id, student_name FROM Students").use { if (it.moveToFirst()) { do { students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2))) @@ -33,9 +34,9 @@ class Migration27 : Migration(26, 27) { return students } - private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList> { + private fun getReportingUnits(db: SupportSQLiteDatabase): MutableList> { val units = mutableListOf>() - database.query("SELECT sender_id, sender_name FROM ReportingUnits").use { + db.query("SELECT sender_id, sender_name FROM ReportingUnits").use { if (it.moveToFirst()) { do { units.add(it.getInt(0) to it.getString(1)) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt index 51e7628b5..e8a5a4a86 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration28 : Migration(27, 28) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Conferences ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt index 327552d75..dac303d27 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration29 : Migration(28, 29) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS GradesStatistics") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS GradesStatistics") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradeSemesterStatistics ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -16,8 +17,10 @@ class Migration29 : Migration(28, 29) { amounts TEXT NOT NULL, student_grade INTEGER NOT NULL ) - """) - database.execSQL(""" + """ + ) + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradePartialStatistics ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt index d9699c0f4..44d421648 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration3.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration3 : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS CompletedLesson ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt index b33914fec..3fea8ec0e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration30 : Migration(29, 30) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE TimetableAdditional ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, @@ -16,6 +17,7 @@ class Migration30 : Migration(29, 30) { date INTEGER NOT NULL, subject TEXT NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt index 064a3e5bc..28fb10562 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration31.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration31 : Migration(30, 31) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( """CREATE TABLE IF NOT EXISTS StudentInfo ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt index 508485e08..347873936 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration32 : Migration(31, 32) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt index 4a57880d4..9778d2790 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration33.kt @@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration33 : Migration(32, 33) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS StudentInfo") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS StudentInfo") - database.execSQL( + db.execSQL( """CREATE TABLE IF NOT EXISTS StudentInfo ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, student_id INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt index 2c57eb00a..e9eec58cd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration34.kt @@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration34 : Migration(33, 34) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM ReportingUnits") - database.execSQL("DELETE FROM Recipients") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM ReportingUnits") + db.execSQL("DELETE FROM Recipients") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt index f63431d00..b238ce8b4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration35.kt @@ -7,13 +7,13 @@ import io.github.wulkanowy.utils.AppInfo class Migration35(private val appInfo: AppInfo) : Migration(34, 35) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0") - database.query("SELECT * FROM Students").use { + db.query("SELECT * FROM Students").use { while (it.moveToNext()) { val studentId = it.getLongOrNull(0) - database.execSQL( + db.execSQL( """ UPDATE Students SET avatar_color = ${appInfo.defaultColorsForAvatar.random()} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt index 7ea106585..62ce346cd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration36.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration36 : Migration(35, 36) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") - database.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt index a3fcd51a6..9ab35514f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration37.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration37 : Migration(36, 37) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( """ CREATE TABLE IF NOT EXISTS TimetableHeaders ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt index 1f90f5a44..bb9b32bfa 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration38.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration38 : Migration(37, 38) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, @@ -14,6 +15,7 @@ class Migration38 : Migration(37, 38) { `subject` TEXT NOT NULL, `content` TEXT NOT NULL ) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt index 6c0d36dd2..2e5315bf4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration39.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration39 : Migration(38, 39) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") - database.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt index 0ae89bdd6..b6089aa62 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration4.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration4 : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY NOT NULL, is_notified INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt index 6d2795c7c..8e38b0c84 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration40.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration40 : Migration(39, 40) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Notifications` ( `student_id` INTEGER NOT NULL, @@ -20,4 +20,4 @@ class Migration40 : Migration(39, 40) { """ ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt index ccaf85755..bfc28334b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration41.kt @@ -7,9 +7,9 @@ import io.github.wulkanowy.data.enums.GradeExpandMode class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) { - override fun migrate(database: SupportSQLiteDatabase) { + override fun migrate(db: SupportSQLiteDatabase) { migrateSharedPreferences() - database.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0") } private fun migrateSharedPreferences() { @@ -18,4 +18,4 @@ class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migratio } sharedPrefProvider.delete("pref_key_expand_grade") } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt index 3d66f301b..14356e279 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration42.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration42 : Migration(41, 42) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( """CREATE TABLE IF NOT EXISTS `AdminMessages` ( `id` INTEGER NOT NULL, `title` TEXT NOT NULL, @@ -21,4 +21,4 @@ class Migration42 : Migration(41, 42) { PRIMARY KEY(`id`))""" ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt index 68c2834d6..ef8108166 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt @@ -5,8 +5,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration43 : Migration(42, 43) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") - database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt index 7bdcab5f4..0a4e5f962 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration44.kt @@ -5,7 +5,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration44 : Migration(43, 44) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt index d3fa5cf93..0bacbaa0e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration46.kt @@ -8,65 +8,65 @@ import java.time.ZoneOffset class Migration46 : Migration(45, 46) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateConferences(database) - migrateMessages(database) - migrateMobileDevices(database) - migrateNotifications(database) - migrateTimetable(database) - migrateTimetableAdditional(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateConferences(db) + migrateMessages(db) + migrateMobileDevices(db) + migrateNotifications(db) + migrateTimetable(db) + migrateTimetableAdditional(db) } - private fun migrateConferences(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM Conferences").use { + private fun migrateConferences(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Conferences").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) val timestampUtc = timestampLocal.timestampLocalToUTC() - database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id") + db.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id") } } } - private fun migrateMessages(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM Messages").use { + private fun migrateMessages(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Messages").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) val timestampUtc = timestampLocal.timestampLocalToUTC() - database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id") + db.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id") } } } - private fun migrateMobileDevices(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM MobileDevices").use { + private fun migrateMobileDevices(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM MobileDevices").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) val timestampUtc = timestampLocal.timestampLocalToUTC() - database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id") + db.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id") } } } - private fun migrateNotifications(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM Notifications").use { + private fun migrateNotifications(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Notifications").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date")) val timestampUtc = timestampLocal.timestampLocalToUTC() - database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id") + db.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id") } } } - private fun migrateTimetable(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM Timetable").use { + private fun migrateTimetable(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM Timetable").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) @@ -74,13 +74,13 @@ class Migration46 : Migration(45, 46) { val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() - database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + db.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") } } } - private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) { - database.query("SELECT * FROM TimetableAdditional").use { + private fun migrateTimetableAdditional(db: SupportSQLiteDatabase) { + db.query("SELECT * FROM TimetableAdditional").use { while (it.moveToNext()) { val id = it.getLong(it.getColumnIndexOrThrow("id")) val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start")) @@ -88,7 +88,7 @@ class Migration46 : Migration(45, 46) { val timestampUtcStart = timestampLocalStart.timestampLocalToUTC() val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC() - database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") + db.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id") } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt index 6e1de19d4..97766c01e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration49.kt @@ -5,10 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration49 : Migration(48, 49) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements") - database.execSQL( + db.execSQL( """ CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` ( `user_login_id` INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt index dbcd916ba..a5b4e8e1a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration5.kt @@ -7,11 +7,16 @@ import java.time.ZoneOffset class Migration5 : Migration(4, 5) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL") - database.execSQL("UPDATE Students SET registration_date = '${now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()}'") - database.execSQL("DROP TABLE IF EXISTS Notes") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL") + db.execSQL( + "UPDATE Students SET registration_date = '${ + now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() + }'" + ) + db.execSQL("DROP TABLE IF EXISTS Notes") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Notes ( id INTEGER PRIMARY KEY NOT NULL, is_read INTEGER NOT NULL, @@ -21,6 +26,7 @@ class Migration5 : Migration(4, 5) { teacher TEXT NOT NULL, category TEXT NOT NULL, content TEXT NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt index d45a81570..577998ca0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration50.kt @@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration50 : Migration(49, 50) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS MobileDevices") - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS MobileDevices") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `MobileDevices` ( `user_login_id` INTEGER NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt index e78e2e3a7..7023049f9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration51.kt @@ -5,17 +5,17 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration51 : Migration(50, 51) { - override fun migrate(database: SupportSQLiteDatabase) { - createMailboxTable(database) - recreateMessagesTable(database) - recreateMessageAttachmentsTable(database) - recreateRecipientsTable(database) - deleteReportingUnitTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + createMailboxTable(db) + recreateMessagesTable(db) + recreateMessageAttachmentsTable(db) + recreateRecipientsTable(db) + deleteReportingUnitTable(db) } - private fun createMailboxTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Mailboxes") - database.execSQL( + private fun createMailboxTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Mailboxes") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Mailboxes` ( `globalKey` TEXT NOT NULL, @@ -30,9 +30,9 @@ class Migration51 : Migration(50, 51) { ) } - private fun recreateMessagesTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL( + private fun recreateMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Messages` ( `message_global_key` TEXT NOT NULL, @@ -52,9 +52,9 @@ class Migration51 : Migration(50, 51) { ) } - private fun recreateMessageAttachmentsTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS MessageAttachments") - database.execSQL( + private fun recreateMessageAttachmentsTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS MessageAttachments") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `MessageAttachments` ( `real_id` INTEGER NOT NULL, @@ -66,9 +66,9 @@ class Migration51 : Migration(50, 51) { ) } - private fun recreateRecipientsTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Recipients") - database.execSQL( + private fun recreateRecipientsTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Recipients") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Recipients` ( `mailboxGlobalKey` TEXT NOT NULL, @@ -82,7 +82,7 @@ class Migration51 : Migration(50, 51) { ) } - private fun deleteReportingUnitTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS ReportingUnits") + private fun deleteReportingUnitTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS ReportingUnits") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt index 12624a51a..dd9e68c97 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration53.kt @@ -5,14 +5,14 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration53 : Migration(52, 53) { - override fun migrate(database: SupportSQLiteDatabase) { - createMailboxTable(database) - recreateMessagesTable(database) + override fun migrate(db: SupportSQLiteDatabase) { + createMailboxTable(db) + recreateMessagesTable(db) } - private fun createMailboxTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Mailboxes") - database.execSQL( + private fun createMailboxTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Mailboxes") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Mailboxes` ( `globalKey` TEXT NOT NULL, @@ -29,9 +29,9 @@ class Migration53 : Migration(52, 53) { ) } - private fun recreateMessagesTable(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL( + private fun recreateMessagesTable(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( """ CREATE TABLE IF NOT EXISTS `Messages` ( `email` TEXT NOT NULL, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt index 678bd32f2..60bd21f0a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration54.kt @@ -5,22 +5,24 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration54 : Migration(53, 54) { - override fun migrate(database: SupportSQLiteDatabase) { - migrateResman(database) - removeTomaszowMazowieckiStudents(database) + override fun migrate(db: SupportSQLiteDatabase) { + migrateResman(db) + removeTomaszowMazowieckiStudents(db) } - private fun migrateResman(database: SupportSQLiteDatabase) { - database.execSQL(""" + private fun migrateResman(db: SupportSQLiteDatabase) { + db.execSQL( + """ UPDATE Students SET scrapper_base_url = 'https://vulcan.net.pl', login_type = 'ADFSLightScoped', symbol = 'rzeszowprojekt' WHERE scrapper_base_url = 'https://resman.pl' - """.trimIndent()) + """.trimIndent() + ) } - private fun removeTomaszowMazowieckiStudents(database: SupportSQLiteDatabase) { - database.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'") + private fun removeTomaszowMazowieckiStudents(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM Students WHERE symbol = 'tomaszowmazowiecki'") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt index fa9436187..06cd5f0fd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration6.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration6 : Migration(5, 6) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS ReportingUnits ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -15,9 +16,11 @@ class Migration6 : Migration(5, 6) { sender_id INTEGER NOT NULL, sender_name TEXT NOT NULL, roles TEXT NOT NULL) - """) + """ + ) - database.execSQL(""" + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Recipients ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -28,10 +31,11 @@ class Migration6 : Migration(5, 6) { unit_id INTEGER NOT NULL, role INTEGER NOT NULL, hash TEXT NOT NULL) - """) + """ + ) - database.execSQL("DELETE FROM Semesters WHERE 1") - database.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") - database.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("DELETE FROM Semesters WHERE 1") + db.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL") + db.execSQL("ALTER TABLE Semesters ADD COLUMN unit_id INTEGER DEFAULT 0 NOT NULL") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt index 120716c81..83a822f22 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration7.kt @@ -5,8 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration7 : Migration(6, 7) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ CREATE TABLE IF NOT EXISTS GradesStatistics ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, @@ -15,6 +16,7 @@ class Migration7 : Migration(6, 7) { grade INTEGER NOT NULL, amount INTEGER NOT NULL, is_semester INTEGER NOT NULL) - """) + """ + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt index 7009ee129..992e8c68d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration8.kt @@ -5,9 +5,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration8 : Migration(7, 8) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL") - database.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL") - database.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL") + db.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL") + db.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL") } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt index d79a57062..b83c34c41 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration9.kt @@ -5,9 +5,10 @@ import androidx.sqlite.db.SupportSQLiteDatabase class Migration9 : Migration(8, 9) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("DROP TABLE IF EXISTS Messages") - database.execSQL(""" + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS Messages") + db.execSQL( + """ CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY NOT NULL, student_id INTEGER NOT NULL, diff --git a/build.gradle b/build.gradle index 3fafd2d24..e2b82e9a3 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,3 @@ allprojects { maven { url "https://developer.huawei.com/repo/" } } } - -tasks.register('clean', Delete) { - delete rootProject.buildDir -} From 6802d740020f452348de8c7b5dc374f2ff49ae33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:31:05 +0000 Subject: [PATCH 317/545] Bump com.google.firebase:firebase-bom from 32.4.1 to 32.5.0 (#2341) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3a90d3fb9..661ebf87e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.4.1') + playImplementation platform('com.google.firebase:firebase-bom:32.5.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 3bf27baed5f276a148f43b733157bf1d48ef9251 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:31:24 +0000 Subject: [PATCH 318/545] Bump org.robolectric:robolectric from 4.11 to 4.11.1 (#2342) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 661ebf87e..78a6e084d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -272,7 +272,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.11' + testImplementation 'org.robolectric:robolectric:4.11.1' testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" From 06b6d88dd1179f6d8b8f03f3555af061a1e0fb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 3 Nov 2023 23:05:35 +0100 Subject: [PATCH 319/545] Version 2.2.5 --- app/build.gradle | 6 +++--- .../ui/modules/grade/summary/GradeSummaryPresenter.kt | 2 +- app/src/main/play/release-notes/pl-PL/default.txt | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 78a6e084d..2feeaac08 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 136 - versionName "2.2.4" + versionCode 137 + versionName "2.2.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.4' + implementation 'io.github.wulkanowy:sdk:2.2.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 4d5a43d8f..32508ff6f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -42,7 +42,7 @@ class GradeSummaryPresenter @Inject constructor( val student = studentRepository.getCurrentStudent() averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) } - .logResourceStatus("load grade summary", showData = true) + .logResourceStatus("load grade summary") .mapResourceData { createGradeSummaryItems(it) } .onResourceData { view?.run { 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 907221d79..1f494a355 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,5 @@ -Wersja 2.2.4 +Wersja 2.2.5 -— ułatwiliśmy przełączenie dnia na weekend w planie lekcji przy użyciu strzałek -— poprawiliśmy wsparcie dla statystyk ocen z systemem punktowym -— poprawiliśmy sortowanie nauczycieli w widoku Szkoła i nauczyciele +— naprawiliśmy logowanie do aplikacji, które zostało zepsute w piątek aktualizacją dziennika VULCAN Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 7fa9219c7ba83d027b4056ba180575791f0c419d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 6 Nov 2023 09:40:47 +0100 Subject: [PATCH 320/545] Bump sdk version --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2feeaac08..3f7f33cb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.5' + implementation 'io.github.wulkanowy:sdk:2.2.6-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' From ce9cb351726578680ff96d5b44fd9a2f70940ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 6 Nov 2023 11:22:15 +0100 Subject: [PATCH 321/545] Version 2.2.6 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3f7f33cb7..7448b799a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 137 - versionName "2.2.5" + versionCode 138 + versionName "2.2.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,8 +161,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.01d - updatePriority = 0 + userFraction = 0.99d + updatePriority = 5 enabled.set(false) } @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.6-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.2.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 1f494a355..bb2266809 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,5 @@ -Wersja 2.2.5 +Wersja 2.2.6 -— naprawiliśmy logowanie do aplikacji, które zostało zepsute w piątek aktualizacją dziennika VULCAN +— naprawiliśmy logowanie do aplikacji (tym razem musi się udać) na odmianie standardowej i podobnych Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 037cbb0b1971de04da5992bb7ee6a95b27dc6d48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 23:08:13 +0100 Subject: [PATCH 322/545] Bump about_libraries from 10.9.1 to 10.9.2 (#2344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mikołaj Pich --- .idea/migrations.xml | 10 ++++++++++ app/build.gradle | 2 +- build.gradle | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .idea/migrations.xml diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 000000000..f8051a6f9 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7448b799a..6d6314661 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,7 +192,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.6' + implementation 'io.github.wulkanowy:sdk:2.2.7-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/build.gradle b/build.gradle index e2b82e9a3..12e3bd972 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.10' - about_libraries = '10.9.1' + about_libraries = '10.9.2' hilt_version = '2.48.1' } repositories { From 650cf7484e177ad11c5f0c1b82f14474d431c119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:10:11 +0000 Subject: [PATCH 323/545] Bump android_hilt from 1.0.0 to 1.1.0 (#2343) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6d6314661..d97a89df8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,7 +184,7 @@ huaweiPublish { ext { work_manager = "2.8.1" - android_hilt = "1.0.0" + android_hilt = "1.1.0" room = "2.6.0" chucker = "3.5.2" mockk = "1.13.8" From f61b6a5e78b6c80eb0c9944e7e6d09f688f7d0c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:10:32 +0000 Subject: [PATCH 324/545] Bump com.google.android.play:integrity from 1.2.0 to 1.3.0 (#2351) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d97a89df8..20683d27c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -254,7 +254,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-config-ktx' playImplementation 'com.google.android.gms:play-services-ads:22.4.0' - playImplementation "com.google.android.play:integrity:1.2.0" + playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' From 01f892ce5ca314f3e1d1ea157a309f4cf06f3c5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:19:53 +0000 Subject: [PATCH 325/545] Bump com.android.tools.build:gradle from 8.1.2 to 8.1.4 (#2356) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 12e3bd972..a61fad1d1 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.13" - classpath 'com.android.tools.build:gradle:8.1.2' + classpath 'com.android.tools.build:gradle:8.1.4' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.301' From 643ad60455cba661ba72d99329a7d71fc0dbb767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:20:18 +0000 Subject: [PATCH 326/545] Bump com.google.firebase:firebase-bom from 32.5.0 to 32.6.0 (#2355) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 20683d27c..1bbb8c375 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.5.0') + playImplementation platform('com.google.firebase:firebase-bom:32.6.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From e9540b4012696df3cdb1132a60c969d5916145c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:20:41 +0000 Subject: [PATCH 327/545] Bump androidx.activity:activity-ktx from 1.8.0 to 1.8.1 (#2354) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1bbb8c375..7038485c9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -201,7 +201,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.8.0" + implementation "androidx.activity:activity-ktx:1.8.1" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.6.1" implementation "androidx.annotation:annotation:1.7.0" From 17caa8ecbda7cba09b9804266da16f89ba5f47d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:21:00 +0000 Subject: [PATCH 328/545] Bump com.android.tools:desugar_jdk_libs from 2.0.3 to 2.0.4 (#2345) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7038485c9..3432aa319 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,7 @@ ext { dependencies { implementation 'io.github.wulkanowy:sdk:2.2.7-SNAPSHOT' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" From 59d46ce956eadf36b87be3da33f6bdac7a65e5a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:21:19 +0000 Subject: [PATCH 329/545] Bump com.google.android.gms:play-services-ads from 22.4.0 to 22.5.0 (#2346) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3432aa319..6dbd5d557 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -253,7 +253,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-config-ktx' - playImplementation 'com.google.android.gms:play-services-ads:22.4.0' + playImplementation 'com.google.android.gms:play-services-ads:22.5.0' playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' From e82ac78d4a47bb361918dc2d1a91f6fab2f971f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:32:18 +0000 Subject: [PATCH 330/545] Bump androidx.fragment:fragment-ktx from 1.6.1 to 1.6.2 (#2348) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6dbd5d557..b4ea30a83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -203,7 +203,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.8.1" implementation "androidx.appcompat:appcompat:1.6.1" - implementation "androidx.fragment:fragment-ktx:1.6.1" + implementation "androidx.fragment:fragment-ktx:1.6.2" implementation "androidx.annotation:annotation:1.7.0" implementation "androidx.preference:preference-ktx:1.2.1" From 2c40c221c356d8c552673b5037589290694931a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:32:32 +0000 Subject: [PATCH 331/545] Bump kotlin_version from 1.9.10 to 1.9.21 (#2357) --- build.gradle | 4 ++-- gradle.properties | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a61fad1d1..7ae2426bd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.9.10' + kotlin_version = '1.9.21' about_libraries = '10.9.2' hilt_version = '2.48.1' } @@ -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.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.13" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.15" classpath 'com.android.tools.build:gradle:8.1.4' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' diff --git a/gradle.properties b/gradle.properties index 4c54d414a..7f8fd20a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,3 +11,5 @@ android.defaults.buildfeatures.buildconfig=true # https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-common-faq-0000001063210244#section17273113244910 apmsInstrumentationEnabled=false +# https://community.sonarsource.com/t/sonarscanner-for-gradle-you-can-now-decide-when-to-compile/102069/2 +systemProp.sonar.gradle.skipCompile=true From 137c30529579269833ca78cd7dbd55f857fb401f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:44:23 +0000 Subject: [PATCH 332/545] Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2365) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b4ea30a83..4985f96ae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation 'androidx.core:core-ktx:1.12.0' From b4c0440a8e62aaef78dd279472f926466ac838fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:44:37 +0000 Subject: [PATCH 333/545] Bump hilt_version from 2.48.1 to 2.49 (#2362) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7ae2426bd..f45031b2f 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.21' about_libraries = '10.9.2' - hilt_version = '2.48.1' + hilt_version = '2.49' } repositories { mavenCentral() From 003d63b516286d2599f08437f50af522fd093523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:44:50 +0000 Subject: [PATCH 334/545] Bump room from 2.6.0 to 2.6.1 (#2360) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4985f96ae..c8f9515e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -185,7 +185,7 @@ huaweiPublish { ext { work_manager = "2.8.1" android_hilt = "1.1.0" - room = "2.6.0" + room = "2.6.1" chucker = "3.5.2" mockk = "1.13.8" coroutines = "1.7.3" From 0d950fbd860980699b4a3a436b6a3dcb5f48a06e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:52:35 +0000 Subject: [PATCH 335/545] Bump com.google.android.gms:play-services-ads from 22.5.0 to 22.6.0 (#2367) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c8f9515e0..391eb7a11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -253,7 +253,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-config-ktx' - playImplementation 'com.google.android.gms:play-services-ads:22.5.0' + playImplementation 'com.google.android.gms:play-services-ads:22.6.0' playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' From eceef3f58297be314701d911a2dfde019f853b9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:52:54 +0000 Subject: [PATCH 336/545] Bump com.google.firebase:firebase-bom from 32.6.0 to 32.7.0 (#2366) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 391eb7a11..0aecc86ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.6.0') + playImplementation platform('com.google.firebase:firebase-bom:32.7.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-crashlytics:' From 784ee583840417014ff173a1de38708dc380732a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:21:08 +0000 Subject: [PATCH 337/545] Bump work_manager from 2.8.1 to 2.9.0 (#2363) --- app/build.gradle | 2 +- .../main/java/io/github/wulkanowy/WulkanowyApp.kt | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0aecc86ac..938392d30 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -183,7 +183,7 @@ huaweiPublish { } ext { - work_manager = "2.8.1" + work_manager = "2.9.0" android_hilt = "1.1.0" room = "2.6.1" chucker = "3.5.2" diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index dc1061018..2e8ca6e67 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -1,7 +1,9 @@ package io.github.wulkanowy import android.app.Application -import android.util.Log.* +import android.util.Log.DEBUG +import android.util.Log.INFO +import android.util.Log.VERBOSE import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration import com.yariksoffice.lingver.Lingver @@ -9,7 +11,14 @@ import dagger.hilt.android.HiltAndroidApp import fr.bipi.treessence.file.FileLoggerTree import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.ActivityLifecycleLogger +import io.github.wulkanowy.utils.AdsHelper +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.CrashLogExceptionTree +import io.github.wulkanowy.utils.CrashLogTree +import io.github.wulkanowy.utils.DebugLogTree +import io.github.wulkanowy.utils.RemoteConfigHelper import timber.log.Timber import javax.inject.Inject @@ -75,7 +84,7 @@ class WulkanowyApp : Application(), Configuration.Provider { } } - override fun getWorkManagerConfiguration() = Configuration.Builder() + override val workManagerConfiguration: Configuration = Configuration.Builder() .setWorkerFactory(workerFactory) .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) .build() From 71ebf1260b56946e14c22e612df6f365a839de71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:44:32 +0000 Subject: [PATCH 338/545] Bump com.android.tools.build:gradle from 8.1.4 to 8.2.0 (#2361) --- app/build.gradle | 15 ++++++++------- app/jacoco.gradle | 2 +- .../java/io/github/wulkanowy/WulkanowyApp.kt | 17 +++++++++-------- build.gradle | 2 +- gradle.properties | 1 - 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 938392d30..83db44f96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,6 +113,7 @@ android { buildFeatures { viewBinding true + buildConfig true } bundle { @@ -186,7 +187,7 @@ ext { work_manager = "2.9.0" android_hilt = "1.1.0" room = "2.6.1" - chucker = "3.5.2" + chucker = "4.0.0" mockk = "1.13.8" coroutines = "1.7.3" } @@ -201,10 +202,10 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.8.1" + implementation "androidx.activity:activity-ktx:1.8.2" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.6.2" - implementation "androidx.annotation:annotation:1.7.0" + implementation "androidx.annotation:annotation:1.7.1" implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.recyclerview:recyclerview:1.3.2" @@ -217,7 +218,7 @@ dependencies { implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation 'com.github.lopspower:CircularImageView:4.3.0' - implementation "androidx.work:work-runtime-ktx:$work_manager" + implementation "androidx.work:work-runtime:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" @@ -250,7 +251,7 @@ dependencies { playImplementation platform('com.google.firebase:firebase-bom:32.7.0') playImplementation 'com.google.firebase:firebase-analytics-ktx' - playImplementation 'com.google.firebase:firebase-messaging:' + playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-config-ktx' playImplementation 'com.google.android.gms:play-services-ads:22.6.0' @@ -261,9 +262,9 @@ dependencies { hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' - releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" + releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" - debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" + debugImplementation "com.github.chuckerteam.chucker:library:$chucker" debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04' diff --git a/app/jacoco.gradle b/app/jacoco.gradle index 434b9218b..67ffdb13b 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -1,7 +1,7 @@ apply plugin: "jacoco" jacoco { - toolVersion "0.8.10" + toolVersion "0.8.11" reportsDirectory.set(file("$buildDir/reports")) } diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 2e8ca6e67..cc4d5a026 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -25,9 +25,6 @@ import javax.inject.Inject @HiltAndroidApp class WulkanowyApp : Application(), Configuration.Provider { - @Inject - lateinit var workerFactory: HiltWorkerFactory - @Inject lateinit var themeManager: ThemeManager @@ -46,6 +43,15 @@ class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var remoteConfigHelper: RemoteConfigHelper + @Inject + lateinit var workerFactory: HiltWorkerFactory + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) + .build() + override fun onCreate() { super.onCreate() initializeAppLanguage() @@ -83,9 +89,4 @@ class WulkanowyApp : Application(), Configuration.Provider { analyticsHelper.logEvent("language", "startup" to preferencesRepository.appLanguage) } } - - override val workManagerConfiguration: Configuration = Configuration.Builder() - .setWorkerFactory(workerFactory) - .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) - .build() } diff --git a/build.gradle b/build.gradle index f45031b2f..6f2e22ac5 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.15" - classpath 'com.android.tools.build:gradle:8.1.4' + classpath 'com.android.tools.build:gradle:8.2.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.301' diff --git a/gradle.properties b/gradle.properties index 7f8fd20a0..99305ac50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,6 @@ 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 From 7f4539fd2799b4a44796d30ff3b2e03f99eb4702 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:48:13 +0000 Subject: [PATCH 339/545] Bump com.google.android.material:material from 1.10.0 to 1.11.0 (#2368) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 83db44f96..83215ceac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -213,7 +213,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.10.0" + implementation "com.google.android.material:material:1.11.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 5ceee84f0e2e710a285f89d4ab90115848ab166c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 06:54:09 +0000 Subject: [PATCH 340/545] Bump com.huawei.agconnect:agconnect-crash from 1.9.1.301 to 1.9.1.302 (#2374) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 83215ceac..aeda6ea59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -260,7 +260,7 @@ dependencies { playImplementation 'com.google.android.play:review-ktx:2.0.1' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.302' releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" From c63a7c03f16af8755046d714df17aabb7f47e1bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 06:54:28 +0000 Subject: [PATCH 341/545] Bump com.huawei.agconnect:agcp from 1.9.1.301 to 1.9.1.302 (#2373) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6f2e22ac5..b0959232e 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.2.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' - classpath 'com.huawei.agconnect:agcp:1.9.1.301' + classpath 'com.huawei.agconnect:agcp:1.9.1.302' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" From 9e013f7cd95c8efde27914be320c3d9ecb9dc276 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 07:00:42 +0000 Subject: [PATCH 342/545] Bump ru.cian:huawei-publish-gradle-plugin from 1.4.0 to 1.4.2 (#2370) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b0959232e..5ba0676db 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { classpath 'com.huawei.agconnect:agcp:1.9.1.302' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" - classpath "ru.cian:huawei-publish-gradle-plugin:1.4.0" + classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" From 23d989d22a5e089e65856a721ca07914780a3284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 07:01:40 +0000 Subject: [PATCH 343/545] Bump hilt_version from 2.49 to 2.50 (#2372) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5ba0676db..8727b9b12 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.21' about_libraries = '10.9.2' - hilt_version = '2.49' + hilt_version = '2.50' } repositories { mavenCentral() From 75f496b5d2f857150ba1915a5a63f0d5ce2e3d6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 16:11:58 +0000 Subject: [PATCH 344/545] Bump kotlin_version from 1.9.21 to 1.9.22 (#2371) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8727b9b12..1e1a6dad6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.9.21' + kotlin_version = '1.9.22' about_libraries = '10.9.2' hilt_version = '2.50' } @@ -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.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.15" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16" classpath 'com.android.tools.build:gradle:8.2.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' From 5646befbd7d514b7134118e8edf9bfa0d882f6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 27 Dec 2023 21:52:04 +0100 Subject: [PATCH 345/545] Revert "Bump com.google.android.material:material from 1.10.0 to 1.11.0 (#2368)" (#2376) This reverts commit 7f4539fd2799b4a44796d30ff3b2e03f99eb4702. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index aeda6ea59..ce254d039 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -213,7 +213,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.11.0" + implementation "com.google.android.material:material:1.10.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 a5bc45c5da88829a2215d3011d94163ba3fc37e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 27 Dec 2023 22:21:07 +0100 Subject: [PATCH 346/545] Version 2.2.7 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ce254d039..27f102891 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 138 - versionName "2.2.6" + versionCode 139 + versionName "2.2.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.7-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.2.7' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 bb2266809..bbb733809 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,5 @@ -Wersja 2.2.6 +Wersja 2.2.7 -— naprawiliśmy logowanie do aplikacji (tym razem musi się udać) na odmianie standardowej i podobnych +— naprawiliśmy logowanie do aplikacji i odświeżanie danych na odmianie standardowej i podobnych Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From c812310497d6cff0dfe553c1745584556a048f40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:36:02 +0100 Subject: [PATCH 347/545] Bump about_libraries from 10.9.2 to 10.10.0 (#2380) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1e1a6dad6..6ba8d651e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.22' - about_libraries = '10.9.2' + about_libraries = '10.10.0' hilt_version = '2.50' } repositories { From e2f2e21081a0fe77d06824c0a1310d4b34b3f4ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 17:38:29 +0000 Subject: [PATCH 348/545] Bump com.huawei.agconnect:agconnect-crash from 1.9.1.302 to 1.9.1.303 (#2379) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 27f102891..5524579a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -260,7 +260,7 @@ dependencies { playImplementation 'com.google.android.play:review-ktx:2.0.1' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.302' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" From d811cdb91905cb4292d986778cba626ee31f5af8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 17:39:08 +0000 Subject: [PATCH 349/545] Bump com.huawei.agconnect:agcp from 1.9.1.302 to 1.9.1.303 (#2377) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6ba8d651e..59735b611 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath 'com.android.tools.build:gradle:8.2.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' - classpath 'com.huawei.agconnect:agcp:1.9.1.302' + classpath 'com.huawei.agconnect:agcp:1.9.1.303' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2" From 7dfa48bbe3d0aa289dba3f815a06b9d1cf943a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 1 Jan 2024 21:19:00 +0100 Subject: [PATCH 350/545] Add User Messaging Platform SDK for ads agreements (#2375) --- app/build.gradle | 6 +- .../io/github/wulkanowy/utils/AdsHelper.kt | 5 +- .../io/github/wulkanowy/utils/AdsHelper.kt | 4 +- .../java/io/github/wulkanowy/WulkanowyApp.kt | 5 - .../repositories/PreferencesRepository.kt | 23 ++--- .../modules/dashboard/DashboardPresenter.kt | 63 ++++++++++--- .../wulkanowy/ui/modules/main/MainActivity.kt | 51 +++------- .../ui/modules/main/MainPresenter.kt | 19 +--- .../wulkanowy/ui/modules/main/MainView.kt | 4 - .../main/res/layout/dialog_ads_consent.xml | 79 ---------------- app/src/main/res/values-cs/strings.xml | 7 -- app/src/main/res/values-da-rDK/strings.xml | 7 -- app/src/main/res/values-de/strings.xml | 7 -- app/src/main/res/values-es-rES/strings.xml | 7 -- app/src/main/res/values-it-rIT/strings.xml | 7 -- app/src/main/res/values-pl/strings.xml | 7 -- app/src/main/res/values-ru/strings.xml | 7 -- app/src/main/res/values-sk/strings.xml | 7 -- app/src/main/res/values-uk/strings.xml | 7 -- app/src/main/res/values/preferences_keys.xml | 3 +- app/src/main/res/values/strings.xml | 9 +- .../ui/modules/settings/ads/AdsFragment.kt | 66 ++++--------- .../ui/modules/settings/ads/AdsPresenter.kt | 40 ++------ .../ui/modules/settings/ads/AdsView.kt | 6 -- .../io/github/wulkanowy/utils/AdsHelper.kt | 94 +++++++++++++++---- .../github/wulkanowy/utils/AnalyticsHelper.kt | 11 +-- .../play/res/xml/scheme_preferences_ads.xml | 20 ++-- 27 files changed, 201 insertions(+), 370 deletions(-) delete mode 100644 app/src/main/res/layout/dialog_ads_consent.xml diff --git a/app/build.gradle b/app/build.gradle index 5524579a4..27fbc7cc4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,14 +250,16 @@ dependencies { implementation 'org.apache.commons:commons-text:1.11.0' playImplementation platform('com.google.firebase:firebase-bom:32.7.0') - playImplementation 'com.google.firebase:firebase-analytics-ktx' + playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' - playImplementation 'com.google.firebase:firebase-config-ktx' + playImplementation 'com.google.firebase:firebase-config' + playImplementation 'com.google.android.gms:play-services-ads:22.6.0' playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' + playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt index 461d29951..3a3b5948f 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -5,6 +5,7 @@ import android.view.View import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject @Suppress("unused") @@ -13,9 +14,11 @@ class AdsHelper @Inject constructor( private val preferencesRepository: PreferencesRepository ) { + val isMobileAdsSdkInitialized = MutableStateFlow(false) + val canShowAd = false + fun initialize() { preferencesRepository.isAdsEnabled = false - preferencesRepository.isAgreeToProcessData = false preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS } diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt index 0e9227022..165a6204f 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -5,6 +5,7 @@ import android.view.View import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.modules.dashboard.DashboardItem +import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject @Suppress("unused") @@ -12,10 +13,11 @@ class AdsHelper @Inject constructor( @ApplicationContext private val context: Context, private val preferencesRepository: PreferencesRepository ) { + val isMobileAdsSdkInitialized = MutableStateFlow(false) + val canShowAd = false fun initialize() { preferencesRepository.isAdsEnabled = false - preferencesRepository.isAgreeToProcessData = false preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS } diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index cc4d5a026..38fade0a6 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -12,7 +12,6 @@ import fr.bipi.treessence.file.FileLoggerTree import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.utils.ActivityLifecycleLogger -import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.CrashLogExceptionTree @@ -37,9 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var analyticsHelper: AnalyticsHelper - @Inject - lateinit var adsHelper: AdsHelper - @Inject lateinit var remoteConfigHelper: RemoteConfigHelper @@ -56,7 +52,6 @@ class WulkanowyApp : Application(), Configuration.Provider { super.onCreate() initializeAppLanguage() themeManager.applyDefaultTheme() - adsHelper.initialize() remoteConfigHelper.initialize() initLogging() } 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 495415f9f..64e60a60b 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 @@ -9,7 +9,12 @@ import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Serializer import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R -import io.github.wulkanowy.data.enums.* +import io.github.wulkanowy.data.enums.AppTheme +import io.github.wulkanowy.data.enums.GradeColorTheme +import io.github.wulkanowy.data.enums.GradeExpandMode +import io.github.wulkanowy.data.enums.GradeSortingMode +import io.github.wulkanowy.data.enums.TimetableGapsMode +import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem @@ -18,7 +23,7 @@ import kotlinx.coroutines.flow.map import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Instant -import java.util.* +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @@ -303,19 +308,6 @@ class PreferencesRepository @Inject constructor( get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false) set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) } - var isAgreeToProcessData: Boolean - get() = getBoolean( - R.string.pref_key_ads_consent_data_processing, - R.bool.pref_default_ads_consent_data_processing - ) - set(value) = sharedPref.edit { - putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value) - } - - var isPersonalizedAdsEnabled: Boolean - get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false) - set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) } - val isAdsEnabledFlow = flowSharedPref.getBoolean( context.getString(R.string.pref_key_ads_enabled), context.resources.getBoolean(R.bool.pref_default_ads_enabled) @@ -398,7 +390,6 @@ class PreferencesRepository @Inject constructor( private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown" - private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled" private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" } } 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 ae451ae15..c93dd9e78 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 @@ -1,19 +1,46 @@ package io.github.wulkanowy.ui.modules.dashboard -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull 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.data.errorOrNull +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository +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.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.nextOrSameSchoolDay -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import java.time.Instant @@ -48,6 +75,11 @@ class DashboardPresenter @Inject constructor( private val firstLoadedItemList = mutableListOf() + private val selectedDashboardTiles + get() = preferencesRepository.selectedDashboardTiles + .filterNot { it == DashboardItem.Tile.ADS && !adsHelper.canShowAd } + .toSet() + private lateinit var lastError: Throwable override fun onAttachView(view: DashboardView) { @@ -59,10 +91,19 @@ class DashboardPresenter @Inject constructor( showContent(false) } + val selectedDashboardTilesFlow = preferencesRepository.selectedDashboardTilesFlow + .map { selectedDashboardTiles } + val isAdsEnabledFlow = preferencesRepository.isAdsEnabledFlow + .filter { (adsHelper.canShowAd && it) || !it } + .map { selectedDashboardTiles } + val isMobileAdsSdkInitializedFlow = adsHelper.isMobileAdsSdkInitialized + .filter { it } + .map { selectedDashboardTiles } + merge( - preferencesRepository.selectedDashboardTilesFlow, - preferencesRepository.isAdsEnabledFlow - .map { preferencesRepository.selectedDashboardTiles } + selectedDashboardTilesFlow, + isAdsEnabledFlow, + isMobileAdsSdkInitializedFlow ) .onEach { loadData(tilesToLoad = it) } .launch("dashboard_pref") @@ -71,7 +112,7 @@ class DashboardPresenter @Inject constructor( fun onAdminMessageDismissed(adminMessage: AdminMessage) { preferencesRepository.dismissedAdminMessageIds += adminMessage.id - loadData(preferencesRepository.selectedDashboardTiles) + loadData(selectedDashboardTiles) } fun onDragAndDropEnd(list: List) { @@ -187,7 +228,7 @@ class DashboardPresenter @Inject constructor( fun onSwipeRefresh() { Timber.i("Force refreshing the dashboard") - loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true) + loadData(selectedDashboardTiles, forceRefresh = true) } fun onRetry() { @@ -195,7 +236,7 @@ class DashboardPresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true) + loadData(selectedDashboardTiles, forceRefresh = true) } fun onViewReselected() { @@ -216,7 +257,7 @@ class DashboardPresenter @Inject constructor( } fun onDashboardTileSettingsSelected(): Boolean { - view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList()) + view?.showDashboardTileSettings(selectedDashboardTiles.toList()) return true } @@ -232,7 +273,7 @@ class DashboardPresenter @Inject constructor( private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { flow { - val selectedTiles = preferencesRepository.selectedDashboardTiles + val selectedTiles = selectedDashboardTiles val flowSuccess = flowOf(Resource.Success(null)) val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 25ab73bca..ba0ef4050 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -9,7 +9,11 @@ import android.view.MenuItem import android.view.ViewGroup.MarginLayoutParams import androidx.activity.OnBackPressedCallback import androidx.activity.addCallback -import androidx.core.view.* +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.preference.Preference @@ -23,12 +27,19 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityMainBinding -import io.github.wulkanowy.databinding.DialogAdsConsentBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo +import io.github.wulkanowy.utils.InAppReviewHelper +import io.github.wulkanowy.utils.InAppUpdateHelper +import io.github.wulkanowy.utils.createNameInitialsDrawable +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.nickOrName +import io.github.wulkanowy.utils.safelyPopFragments +import io.github.wulkanowy.utils.setOnViewChangeListener import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import timber.log.Timber @@ -312,40 +323,6 @@ class MainActivity : BaseActivity(), MainVie .show() } - override fun showPrivacyPolicyDialog() { - val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater) - - val dialog = MaterialAlertDialogBuilder(this) - .setTitle(R.string.pref_ads_consent_title) - .setMessage(R.string.pref_ads_consent_description) - .setView(dialogAdsConsentBinding.root) - .show() - - dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked -> - dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked - } - - dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener { - presenter.onPrivacyAgree(true) - dialog.dismiss() - } - - dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener { - presenter.onPrivacyAgree(false) - dialog.dismiss() - } - - dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() } - dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() } - } - - override fun openPrivacyPolicy() { - openInternetBrowser( - "https://wulkanowy.github.io/polityka-prywatnosci.html", - ::showMessage - ) - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index ae05ecf22..5469fcad3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -19,7 +19,6 @@ import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import kotlinx.coroutines.launch -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import timber.log.Timber import java.time.Duration @@ -52,6 +51,7 @@ class MainPresenter @Inject constructor( destinationType in rootDestinationTypeList -> { rootDestinationTypeList.indexOf(destinationType) } + else -> 4 } @@ -110,6 +110,7 @@ class MainPresenter @Inject constructor( is AccountView, is StudentInfoView, is AccountDetailsView -> false + else -> true } @@ -148,20 +149,8 @@ class MainPresenter @Inject constructor( } fun onEnableAdsSelected() { - view?.showPrivacyPolicyDialog() - } - - fun onPrivacyAgree(isPersonalizedAds: Boolean) { - preferencesRepository.isAgreeToProcessData = true - preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds - - adsHelper.initialize() - preferencesRepository.isAdsEnabled = true - } - - fun onPrivacySelected() { - view?.openPrivacyPolicy() + adsHelper.initialize() } private fun checkInAppReview() { @@ -189,8 +178,8 @@ class MainPresenter @Inject constructor( .getOrElse { return@launch } if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) { - view?.showAppSupport() preferencesRepository.isAppSupportShown = true + view?.showAppSupport() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 62436f3bf..70a94fc81 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -46,10 +46,6 @@ interface MainView : BaseView { fun showAppSupport() - fun showPrivacyPolicyDialog() - - fun openPrivacyPolicy() - fun openMoreDestination(destination: Destination) interface MainChildView { diff --git a/app/src/main/res/layout/dialog_ads_consent.xml b/app/src/main/res/layout/dialog_ads_consent.xml deleted file mode 100644 index 118fb9c1f..000000000 --- a/app/src/main/res/layout/dialog_ads_consent.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3af494fa4..7f42d1100 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -769,13 +769,6 @@ Ochrana osobních údajů Reklama se načítá Děkujeme za vaši podporu, vraťte se později pro více reklam - Můžeme použít Vaše data k zobrazení reklam? - Volbu můžete kdykoliv změnit v nastavení aplikace. Můžeme použít Vaše data k zobrazení reklam šitých pro vás nebo pomocí méně vašich dat zobrazovat nepřizpůsobené reklamy. Podrobnosti naleznete v našich Zásadách ochrany osobních údajů - Přizpůsobené reklamy - Nepřizpůsobené reklamy - Je mi více než 18 let - Ano, přizpůsobené reklamy - Ano, nepřizpůsobené reklamy Pokročilé Vzhled a chování Oznámení diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index a89b83769..4c22e9733 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -679,13 +679,6 @@ 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 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4c9aa2d28..5d71dd32d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -679,13 +679,6 @@ Datenschutzerklärung Anzeige wird geladen Vielen Dank für Ihre Unterstützung, kommen Sie später wieder für weitere Anzeigen - Können wir Ihre Daten zur Anzeige von Werbung verwenden? - Sie können Ihre Wahl jederzeit in den App-Einstellungen ändern. Wir verwenden Ihre Daten, um auf Sie zugeschnittene Anzeigen anzuzeigen oder unter Verwendung weniger Ihrer Daten nicht personalisierte Werbung anzuzeigen. Bitte lesen Sie unsere Datenschutzerklärung für Details - Personalisierte Werbung - keine personalisierte Werbung - Ich bin über 18 Jahre alt - Ja, personalisierte Werbung - Ja, nicht personalisierte Werbung Erweitert Aussehen & Verhalten Benachrichtigungen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index a89b83769..4c22e9733 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -679,13 +679,6 @@ 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 diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index a89b83769..4c22e9733 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -679,13 +679,6 @@ 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 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 31b9ce32c..0cf4b0898 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -769,13 +769,6 @@ Polityka prywatności Ładowanie reklamy Dziękujemy za wsparcie, wróć później po więcej reklam - Czy możemy używać Twoich danych do wyświetlania reklam? - Możesz zmienić swój wybór w dowolnym momencie w ustawieniach aplikacji. Możemy wykorzystać Twoje dane do wyświetlania reklam dostosowanych do Ciebie lub, przy użyciu mniejszej ilości danych, wyświetlić niepersonalizowane reklamy. Zobacz naszą Politykę Prywatności, aby uzyskać więcej informacji - Spersonalizowane reklamy - Niespersonalizowane reklamy - Mam ukończone 18 lat - Tak, spersonalizowane reklamy - Tak, niespersonalizowane reklamy Zaawansowane Wygląd i zachowanie Powiadomienia diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 89a94a59d..07a9e2021 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -769,13 +769,6 @@ Политика конфиденциальности Реклама загружается Спасибо за вашу поддержку, возвращайтесь позже для дополнительной рекламы - Можем ли мы использовать ваши данные для показа рекламы? - Вы можете изменить свой выбор в любое время в настройках приложения. Мы можем использовать ваши данные для показа объявлений в соответствии с вашими пожеланиями или, используя меньше данных, отображать неперсональную рекламу. Пожалуйста, ознакомьтесь с нашей политикой конфиденциальности для подробностей - Персонализированная реклама - Неперсонализированная реклама - Я старше 18 лет - Да, персонализировать рекламу - Да, не персонализировать рекламу Расширенные Внешний вид и поведение Уведомления diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3cde8152c..3ec341d24 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -769,13 +769,6 @@ Ochrana osobných údajov Reklama sa načítava Ďakujeme za vašu podporu, vráťte sa neskôr pre viac reklám - Môžeme použiť Vaše údaje na zobrazenie reklám? - Voľbu môžete kedykoľvek zmeniť v nastavení aplikácie. Môžeme použiť vaše údaje na zobrazenie reklám šitých pre vás alebo pomocou menej vašich dát zobrazovať neprispôsobené reklamy. Podrobnosti nájdete v našich Zásadách ochrany osobných údajov - Prispôsobené reklamy - Neprispôsobené reklamy - Mám viac ako 18 rokov - Áno, prispôsobené reklamy - Áno, neprispôsobené reklamy Pokročilé Vzhľad a správanie Oznámenia diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 20a917aa4..58dc757fa 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -769,13 +769,6 @@ Політика конфіденційності Реклама завантажується Дякуємо за вашу підтримку, повертайтеся пізніше для більшої кількості реклам - Чи можемо ми використовувати ваші дані для висвітлювання реклами? - Ви можете змінити свій вибір в будь-який час в налаштуваннях додатку. Ми можемо використовувати ваші дані для висвітлювання реклами, адаптованої до вас або, використовуючи менше ваших даних, висвітлювати неперсоналізовану рекламу. Перегляньте нашу Політику конфіденційності для подробиць - Персоналізована реклама - Неперсоналізована реклама - Мені більше 18 років - Так, персоналізована реклама - Так, неперсоналізована реклама Додатково Вигляд та поведінка Сповіщення diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 5afffb649..74af9262c 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -37,8 +37,7 @@ single_ad_support ads_enabled ads_privacy_policy - ads_consent_data_processing - ads_over_eighteen + ads_ump_agreements incognito_mode appearance_menu_order diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5cad09d0b..27c454adb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -749,7 +749,7 @@ Support Privacy Policy Agreements - Consent to processing of data related to ads + Show consent to data processing Show ads in app Watch single ad to support project Consent to data processing @@ -758,13 +758,6 @@ 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 diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index af6a83404..ec6027e98 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -2,19 +2,17 @@ package io.github.wulkanowy.ui.modules.settings.ads import android.os.Bundle import android.view.View -import androidx.preference.CheckBoxPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.databinding.DialogAdsConsentBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.openInternetBrowser import javax.inject.Inject @@ -24,6 +22,9 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { @Inject lateinit var presenter: AdsPresenter + @Inject + lateinit var adsHelper: AdsHelper + override val titleStringId = R.string.pref_settings_ads_title override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -46,11 +47,18 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { true } - findPreference(getString(R.string.pref_key_ads_consent_data_processing)) - ?.setOnPreferenceChangeListener { _, newValue -> - presenter.onConsentSelected(newValue as Boolean) - true - } + findPreference(getString(R.string.pref_key_ads_ump_agreements))?.setOnPreferenceClickListener { + presenter.onUmpAgreementsSelected() + true + } + + findPreference(getString(R.string.pref_key_ads_single_support)) + ?.isEnabled = adsHelper.canShowAd + + findPreference(getString(R.string.pref_key_ads_enabled))?.setOnPreferenceChangeListener { _, newValue -> + presenter.onAdsEnabledSelected(newValue as Boolean) + true + } } override fun showAd(ad: RewardedInterstitialAd) { @@ -59,48 +67,6 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { } } - override fun showPrivacyPolicyDialog() { - val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater) - - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.pref_ads_consent_title) - .setMessage(R.string.pref_ads_consent_description) - .setView(dialogAdsConsentBinding.root) - .setOnCancelListener { presenter.onPrivacyDialogCanceled() } - .show() - - dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked -> - dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked - } - - dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener { - presenter.onPersonalizedAgree() - dialog.dismiss() - } - - dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener { - presenter.onNonPersonalizedAgree() - dialog.dismiss() - } - - dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() } - dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() } - } - - override fun showProcessingDataSummary(isPersonalized: Boolean?) { - val summaryText = isPersonalized?.let { - getString(if (it) R.string.pref_ads_summary_personalized else R.string.pref_ads_summary_non_personalized) - } - - findPreference(getString(R.string.pref_key_ads_consent_data_processing)) - ?.summary = summaryText - } - - override fun setCheckedProcessingData(checked: Boolean) { - findPreference(getString(R.string.pref_key_ads_consent_data_processing)) - ?.isChecked = checked - } - override fun setCheckedAdsEnabled(checked: Boolean) { findPreference(getString(R.string.pref_key_ads_enabled)) ?.isChecked = checked diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt index 28c98e3c3..b3c701541 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.settings.ads -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 @@ -13,18 +12,12 @@ class AdsPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val adsHelper: AdsHelper, - private val preferencesRepository: PreferencesRepository ) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: AdsView) { super.onAttachView(view) view.initView() Timber.i("Settings ads view was initialized") - - view.showProcessingDataSummary( - preferencesRepository.isPersonalizedAdsEnabled.takeIf { - preferencesRepository.isAgreeToProcessData - }) } fun onWatchSingleAdSelected() { @@ -50,38 +43,17 @@ class AdsPresenter @Inject constructor( } } - fun onConsentSelected(isChecked: Boolean) { - if (isChecked) { - view?.showPrivacyPolicyDialog() - } else { - view?.showProcessingDataSummary(null) - view?.setCheckedAdsEnabled(false) - } - } - fun onPrivacySelected() { view?.openPrivacyPolicy() } - fun onPrivacyDialogCanceled() { - view?.setCheckedProcessingData(false) + fun onAdsEnabledSelected(newValue: Boolean) { + if (newValue) { + adsHelper.initialize() + } } - fun onNonPersonalizedAgree() { - preferencesRepository.isPersonalizedAdsEnabled = false - - adsHelper.initialize() - - view?.setCheckedProcessingData(true) - view?.showProcessingDataSummary(false) - } - - fun onPersonalizedAgree() { - preferencesRepository.isPersonalizedAdsEnabled = true - - adsHelper.initialize() - - view?.setCheckedProcessingData(true) - view?.showProcessingDataSummary(true) + fun onUmpAgreementsSelected() { + adsHelper.openAdsUmpAgreements() } } diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt index 8de6e60d3..3b3fa5783 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsView.kt @@ -9,8 +9,6 @@ interface AdsView : BaseView { fun showAd(ad: RewardedInterstitialAd) - fun showPrivacyPolicyDialog() - fun openPrivacyPolicy() fun showLoadingSupportAd(show: Boolean) @@ -18,8 +16,4 @@ interface AdsView : BaseView { fun showWatchAdOncePerVisit(show: Boolean) fun setCheckedAdsEnabled(checked: Boolean) - - fun setCheckedProcessingData(checked: Boolean) - - fun showProcessingDataSummary(isPersonalized: Boolean?) } diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt index d5f65b46d..bd17d52c1 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -1,49 +1,110 @@ package io.github.wulkanowy.utils +import android.app.Activity import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Build -import android.os.Bundle import android.view.View import androidx.core.content.getSystemService -import com.google.ads.mediation.admob.AdMobAdapter -import com.google.android.gms.ads.* +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdSize +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.MobileAds import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback +import com.google.android.ump.ConsentInformation +import com.google.android.ump.ConsentRequestParameters +import com.google.android.ump.UserMessagingPlatform import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ActivityScoped import io.github.wulkanowy.BuildConfig import io.github.wulkanowy.data.repositories.PreferencesRepository +import kotlinx.coroutines.flow.MutableStateFlow +import timber.log.Timber import java.net.UnknownHostException +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine - +@ActivityScoped class AdsHelper @Inject constructor( + private val activity: Activity, @ApplicationContext private val context: Context, - private val preferencesRepository: PreferencesRepository + preferencesRepository: PreferencesRepository ) { + private var isMobileAdsInitializeCalled = AtomicBoolean(false) + private var consentInformation: ConsentInformation? = null + + private val canRequestAd get() = consentInformation?.canRequestAds() == true + val isMobileAdsSdkInitialized = MutableStateFlow(false) + val canShowAd get() = isMobileAdsSdkInitialized.value && canRequestAd + + init { + if (preferencesRepository.isAdsEnabled) { + initialize() + } + } + fun initialize() { - if (preferencesRepository.isAgreeToProcessData) { - MobileAds.initialize(context) + val consentRequestParameters = ConsentRequestParameters.Builder() + .build() + + consentInformation = UserMessagingPlatform.getConsentInformation(context) + consentInformation?.requestConsentInfoUpdate( + activity, + consentRequestParameters, + { + UserMessagingPlatform.loadAndShowConsentFormIfRequired( + activity + ) { loadAndShowError -> + + if (loadAndShowError != null) { + Timber.e(IllegalStateException("${loadAndShowError.errorCode}: ${loadAndShowError.message}")) + } + + if (canRequestAd) { + initializeMobileAds() + } + } + }, + { requestConsentError -> + Timber.e(IllegalStateException("${requestConsentError.errorCode}: ${requestConsentError.message}")) + }) + + if (canRequestAd) { + initializeMobileAds() + } + } + + fun openAdsUmpAgreements() { + UserMessagingPlatform.showPrivacyOptionsForm(activity) { + if (it != null) { + Timber.e(IllegalStateException("${it.errorCode}: ${it.message}")) + } + } + } + + private fun initializeMobileAds() { + if (isMobileAdsInitializeCalled.getAndSet(true)) return + + MobileAds.initialize(context) { + isMobileAdsSdkInitialized.value = true } } suspend fun getSupportAd(): RewardedInterstitialAd? { + if (!canRequestAd) return null if (!context.isInternetConnected()) { throw UnknownHostException() } - val extra = Bundle().apply { putString("npa", "1") } val adRequest = AdRequest.Builder() - .apply { - if (!preferencesRepository.isPersonalizedAdsEnabled) { - addNetworkExtrasBundle(AdMobAdapter::class.java, extra) - } - } .build() return suspendCoroutine { @@ -64,13 +125,8 @@ class AdsHelper @Inject constructor( } suspend fun getDashboardTileAdBanner(width: Int): AdBanner { - val extra = Bundle().apply { putString("npa", "1") } + if (!canShowAd) throw IllegalStateException("Cannot show ad") val adRequest = AdRequest.Builder() - .apply { - if (!preferencesRepository.isPersonalizedAdsEnabled) { - addNetworkExtrasBundle(AdMobAdapter::class.java, extra) - } - } .build() return suspendCoroutine { diff --git a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index 3215fa20c..9ded7e1b6 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -1,25 +1,24 @@ package io.github.wulkanowy.utils import android.app.Activity -import android.content.Context import android.os.Bundle +import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.crashlytics.FirebaseCrashlytics -import dagger.hilt.android.qualifiers.ApplicationContext +import com.google.firebase.analytics.analytics +import com.google.firebase.crashlytics.crashlytics import io.github.wulkanowy.data.repositories.PreferencesRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class AnalyticsHelper @Inject constructor( - @ApplicationContext private val context: Context, preferencesRepository: PreferencesRepository, appInfo: AppInfo, ) { - private val analytics by lazy { FirebaseAnalytics.getInstance(context) } + private val analytics by lazy { Firebase.analytics } - private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } + private val crashlytics by lazy { Firebase.crashlytics } init { if (!appInfo.isDebug) { diff --git a/app/src/play/res/xml/scheme_preferences_ads.xml b/app/src/play/res/xml/scheme_preferences_ads.xml index 4165561a7..444dde3e4 100644 --- a/app/src/play/res/xml/scheme_preferences_ads.xml +++ b/app/src/play/res/xml/scheme_preferences_ads.xml @@ -1,5 +1,11 @@ + @@ -8,25 +14,17 @@ app:key="@string/pref_key_ads_privacy_policy" app:singleLineTitle="false" app:title="@string/pref_ads_privacy_policy" /> - - Date: Tue, 2 Jan 2024 01:30:31 +0100 Subject: [PATCH 351/545] New Crowdin updates (#2381) --- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-da-rDK/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-it-rIT/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7f42d1100..3f0940b58 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -760,7 +760,7 @@ Podpora Ochrana osobních údajů Souhlasy - Souhlas se zpracováním údajů souvisejících s reklamami + Show consent to data processing Zobrazit reklamy v aplikaci Podívejte se na jednu reklamu pro podporu projektu Souhlas se zpracováním dat diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 4c22e9733..512750630 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -670,7 +670,7 @@ Support Privacy Policy Agreements - Consent to processing of data related to ads + Show consent to data processing Show ads in app Watch single ad to support project Consent to data processing diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5d71dd32d..bfc194c03 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -670,7 +670,7 @@ Unterstützung Datenschutz-Bestimmungen Vereinbarungen - Zustimmung zur Verarbeitung von Daten im Zusammenhang mit Anzeigen + Show consent to data processing Anzeigen in der App anzeigen Einzelanzeige ansehen, um Projekt zu unterstützen Einwilligung in die Datenverarbeitung diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 4c22e9733..512750630 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -670,7 +670,7 @@ Support Privacy Policy Agreements - Consent to processing of data related to ads + Show consent to data processing Show ads in app Watch single ad to support project Consent to data processing diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 4c22e9733..512750630 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -670,7 +670,7 @@ Support Privacy Policy Agreements - Consent to processing of data related to ads + Show consent to data processing Show ads in app Watch single ad to support project Consent to data processing diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 0cf4b0898..2872e28e4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -760,7 +760,7 @@ Wsparcie Polityka prywatności Zgody - Zgoda na przetwarzanie danych związanych z reklamami + Pokaż zgodę na przetwarzanie danych Pokazuj reklamy w aplikacji Obejrzyj pojedynczą reklamę, aby wesprzeć projekt Zgoda na przetwarzanie danych diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 07a9e2021..592e9ee8a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -760,7 +760,7 @@ Поддержка Политика приватности Соглашения - Согласие на обработку данных, связанных с объявлениями + Show consent to data processing Показать рекламу в приложении Посмотреть рекламу для поддержки проекта Согласие на обработку данных diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3ec341d24..c9ad645e3 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -760,7 +760,7 @@ Podpora Ochrana osobných údajov Súhlasy - Súhlas so spracovaním údajov súvisiacich s reklamami + Show consent to data processing Zobraziť reklamy v aplikácii Pozrite sa na jednu reklamu pre podporu projektu Súhlas so spracovaním dát diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 58dc757fa..86ee0910b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -760,7 +760,7 @@ Підтримка Політика конфіденційності Угоди - Згода на обробку даних, пов\'язаних з рекламою + Показати згоду на обробку даних Показувати рекламу в додатку Подивіться одну рекламу для підтримки проєкту Згода в обробці даних From f69d50d2c1defaa6be25c4cf6fc5a9dced6e2ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Jan 2024 01:51:09 +0100 Subject: [PATCH 352/545] Version 2.3.0 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 27fbc7cc4..316d11785 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 139 - versionName "2.2.7" + versionCode 140 + versionName "2.3.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,8 +162,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.99d - updatePriority = 5 + userFraction = 0.15d + updatePriority = 3 enabled.set(false) } @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.2.7' + implementation 'io.github.wulkanowy:sdk:2.3.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 bbb733809..89f97bdc5 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.2.7 +Wersja 2.3.0 -— naprawiliśmy logowanie do aplikacji i odświeżanie danych na odmianie standardowej i podobnych +— poprawiliśmy kilka usterek przy odświeżaniu danych (ale pewnie nie wszystkie) +— zaktualizowaliśmy sposób pytania o zgodę na personalizowane reklamy Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 4d1218d1d31e79dd91ac1a03843c05c9b8d98ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 3 Jan 2024 14:53:16 +0100 Subject: [PATCH 353/545] Version 2.3.1 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 3 +-- build.gradle | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 316d11785..60ef0f3c2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 140 - versionName "2.3.0" + versionCode 141 + versionName "2.3.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.1' + implementation 'io.github.wulkanowy:sdk:2.3.3-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 89f97bdc5..2fd7dbee1 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,5 @@ -Wersja 2.3.0 +Wersja 2.3.1 — poprawiliśmy kilka usterek przy odświeżaniu danych (ale pewnie nie wszystkie) -— zaktualizowaliśmy sposób pytania o zgodę na personalizowane reklamy Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases diff --git a/build.gradle b/build.gradle index 59735b611..a0f434e33 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ buildscript { allprojects { repositories { + mavenLocal() mavenCentral() google() maven { url "https://jitpack.io" } From 0aa83b020e884044f8ca00d446051617da1fbcb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 3 Jan 2024 16:01:30 +0100 Subject: [PATCH 354/545] Bump sdk to 2.3.3 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 60ef0f3c2..9f1506bf0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -162,7 +162,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.15d + userFraction = 0.99d updatePriority = 3 enabled.set(false) } @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.3-SNAPSHOT' + implementation 'com.github.wulkanowy:sdk:2.3.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From a3596c35b84268a5c8d63d904a0e2f8ec38f0986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 4 Jan 2024 09:33:51 +0100 Subject: [PATCH 355/545] Update AGP and Gradle (#2385) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 17 +++++++++-------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index a0f434e33..095d1b72f 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.16" - classpath 'com.android.tools.build:gradle:8.2.0' + classpath 'com.android.tools.build:gradle:8.2.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.303' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4cdf41af1ab109bc7f253b2b887023340..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 28216 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 za%0Aoo~v6{I8ONb#igvxd7=aVnxT2gnj@QKwvmG@`A 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 I?qd)S02EvtYybcN diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8838ba97b..e6aba2515 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.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca14..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +145,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=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,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=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From 40df80371cc914e70fd188f523c01cae8f72e7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 5 Jan 2024 16:03:50 +0100 Subject: [PATCH 356/545] Use forked slf4j-timber to fix logging problems with slf4j v2 (#2387) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 9f1506bf0..acfc1bdcf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -240,7 +240,7 @@ dependencies { implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.jakewharton.timber:timber:5.0.1" - implementation "at.favre.lib:slf4j-timber:1.0.1" + implementation 'com.github.Faierbel:slf4j-timber:2.0' implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'io.coil-kt:coil:2.5.0' From 6ee38e9259ca62ec3d3ea030782d157aa0621546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 6 Jan 2024 00:01:33 +0100 Subject: [PATCH 357/545] Add clearing all data and key entry when decryption failed (#2386) --- .../wulkanowy/data/db/dao/StudentDao.kt | 12 +- .../data/repositories/StudentRepository.kt | 30 ++- .../github/wulkanowy/ui/base/BaseActivity.kt | 13 +- .../wulkanowy/ui/base/BaseDialogFragment.kt | 8 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 8 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 29 ++- .../io/github/wulkanowy/ui/base/BaseView.kt | 4 +- .../github/wulkanowy/ui/base/ErrorHandler.kt | 10 +- .../ui/modules/dashboard/DashboardFragment.kt | 7 +- .../ui/modules/settings/SettingsFragment.kt | 4 +- .../settings/advanced/AdvancedFragment.kt | 8 +- .../settings/appearance/AppearanceFragment.kt | 8 +- .../notifications/NotificationsFragment.kt | 8 +- .../ui/modules/settings/sync/SyncFragment.kt | 8 +- .../wulkanowy/utils/security/Scrambler.kt | 193 ++++++++++-------- app/src/main/res/values/strings.xml | 2 + .../ui/modules/settings/ads/AdsFragment.kt | 8 +- 17 files changed, 231 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index d7847c240..d9326ff6c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -1,11 +1,16 @@ package io.github.wulkanowy.data.db.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar -import io.github.wulkanowy.data.db.entities.StudentWithSemesters import javax.inject.Singleton @Singleton @@ -47,6 +52,9 @@ abstract class StudentDao { @Query("UPDATE Students SET is_current = 0") abstract suspend fun resetCurrent() + @Query("DELETE FROM Students WHERE email = :email AND user_name = :userName") + abstract suspend fun deleteByEmailAndUserName(email: String, userName: String) + @Transaction open suspend fun switchCurrent(id: Long) { resetCurrent() 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 2e04224aa..bfad12a8f 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 @@ -1,8 +1,6 @@ package io.github.wulkanowy.data.repositories -import android.content.Context import androidx.room.withTransaction -import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao @@ -17,20 +15,19 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.security.decrypt -import io.github.wulkanowy.utils.security.encrypt +import io.github.wulkanowy.utils.security.Scrambler import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @Singleton class StudentRepository @Inject constructor( - @ApplicationContext private val context: Context, private val dispatchers: DispatchersProvider, private val studentDb: StudentDao, private val semesterDb: SemesterDao, private val sdk: Sdk, - private val appDatabase: AppDatabase + private val appDatabase: AppDatabase, + private val scrambler: Scrambler, ) { suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false @@ -68,7 +65,7 @@ class StudentRepository @Inject constructor( student = student.apply { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { - decrypt(student.password) + scrambler.decrypt(student.password) } } }, @@ -86,7 +83,7 @@ class StudentRepository @Inject constructor( }.apply { if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { - decrypt(student.password) + scrambler.decrypt(student.password) } } } @@ -96,7 +93,7 @@ class StudentRepository @Inject constructor( if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { - decrypt(student.password) + scrambler.decrypt(student.password) } } return student @@ -107,7 +104,7 @@ class StudentRepository @Inject constructor( if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) { student.password = withContext(dispatchers.io) { - decrypt(student.password) + scrambler.decrypt(student.password) } } return student @@ -120,7 +117,7 @@ class StudentRepository @Inject constructor( it.apply { if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) { password = withContext(dispatchers.io) { - encrypt(password, context) + scrambler.encrypt(password) } } } @@ -166,4 +163,15 @@ class StudentRepository @Inject constructor( studentDb.update(studentName) } + + suspend fun deleteStudentsAssociatedWithAccount(student: Student) { + studentDb.deleteByEmailAndUserName(student.email, student.userName) + } + + suspend fun clearAll() { + withContext(dispatchers.io) { + scrambler.clearKeyPair() + appDatabase.clearAllTables() + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index f622209a7..026d38ded 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -68,11 +68,20 @@ abstract class BaseActivity, VB : ViewBinding> : } else Toast.makeText(this, text, Toast.LENGTH_LONG).show() } - override fun showExpiredDialog() { + override fun showExpiredCredentialsDialog() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.main_expired_credentials_title) + .setMessage(R.string.main_expired_credentials_description) + .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmExpiredCredentialsSelected() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + + override fun showDecryptionFailedDialog() { MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) .setMessage(R.string.main_session_relogin) - .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() } + .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmDecryptionFailedSelected() } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 84540b1ca..50e4b05d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -28,8 +28,12 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView Toast.makeText(context, text, Toast.LENGTH_LONG).show() } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun openClearLoginView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index b25346a7e..cec2670b2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -39,8 +39,12 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme } } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showAuthDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 2d913103b..ee92e4fc1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -28,20 +28,37 @@ open class BasePresenter( this.view = view errorHandler.apply { showErrorMessage = view::showError - onSessionExpired = view::showExpiredDialog + onExpiredCredentials = view::showExpiredCredentialsDialog + onDecryptionFailed = view::showDecryptionFailedDialog onNoCurrentStudent = view::openClearLoginView onPasswordChangeRequired = view::showChangePasswordSnackbar onAuthorizationRequired = view::showAuthDialog } } - fun onExpiredLoginSelected() { - Timber.i("Attempt to switch the student after the session expires") + fun onConfirmDecryptionFailedSelected() { + Timber.i("Attempt to clear all data") + + presenterScope.launch { + runCatching { studentRepository.clearAll() } + .onFailure { + Timber.i("Clear data result: An exception occurred") + errorHandler.dispatch(it) + } + .onSuccess { + Timber.i("Clear data result: Open login view") + view?.openClearLoginView() + } + } + } + + fun onConfirmExpiredCredentialsSelected() { + Timber.i("Attempt to delete students associated with the account and switch to new student") presenterScope.launch { runCatching { val student = studentRepository.getCurrentStudent(false) - studentRepository.logoutStudent(student) + studentRepository.deleteStudentsAssociatedWithAccount(student) val students = studentRepository.getSavedStudents(false) if (students.isNotEmpty()) { @@ -50,11 +67,11 @@ open class BasePresenter( } } .onFailure { - Timber.i("Switch student result: An exception occurred") + Timber.i("Delete students result: An exception occurred") errorHandler.dispatch(it) } .onSuccess { - Timber.i("Switch student result: Open login view") + Timber.i("Delete students result: Open login view") view?.openClearLoginView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index b31737e2b..e97a6ab90 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -6,7 +6,9 @@ interface BaseView { fun showMessage(text: String) - fun showExpiredDialog() + fun showExpiredCredentialsDialog() + + fun showDecryptionFailedDialog() fun showAuthDialog() diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index 0a41a47b3..56905709d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -15,7 +15,9 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> } - var onSessionExpired: () -> Unit = {} + var onExpiredCredentials: () -> Unit = {} + + var onDecryptionFailed: () -> Unit = {} var onNoCurrentStudent: () -> Unit = {} @@ -32,7 +34,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co showErrorMessage(context.resources.getErrorString(error), error) when (error) { is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) - is ScramblerException, is BadCredentialsException -> onSessionExpired() + is ScramblerException -> onDecryptionFailed() + is BadCredentialsException -> onExpiredCredentials() is NoCurrentStudentException -> onNoCurrentStudent() is AuthorizationRequiredException -> onAuthorizationRequired() } @@ -40,7 +43,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co open fun clear() { showErrorMessage = { _, _ -> } - onSessionExpired = {} + onExpiredCredentials = {} + onDecryptionFailed = {} onNoCurrentStudent = {} onPasswordChangeRequired = {} onAuthorizationRequired = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index ce17c7632..301262a04 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -30,7 +30,12 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.capitalise +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getErrorString +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.openInternetBrowser +import io.github.wulkanowy.utils.toFormattedString import java.time.LocalDate import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 21f564988..19c4ef6b7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -24,7 +24,9 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin override fun showMessage(text: String) {} - override fun showExpiredDialog() {} + override fun showExpiredCredentialsDialog() {} + + override fun showDecryptionFailedDialog() {} override fun openClearLoginView() {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index 1b8d1a8fa..256b13375 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -47,8 +47,12 @@ class AdvancedFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showMessage(text) } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showChangePasswordSnackbar(redirectUrl: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 70dd694cc..20423eb91 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -63,8 +63,12 @@ class AppearanceFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showMessage(text) } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showChangePasswordSnackbar(redirectUrl: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index af4c4e6ae..2ae983c26 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -133,8 +133,12 @@ class NotificationsFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showMessage(text) } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showChangePasswordSnackbar(redirectUrl: String) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index f48abe9ba..133b1ff44 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -84,8 +84,12 @@ class SyncFragment : PreferenceFragmentCompat(), } } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showChangePasswordSnackbar(redirectUrl: String) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index c994ebab6..db16a2563 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -16,6 +16,7 @@ import android.util.Base64.DEFAULT import android.util.Base64.decode import android.util.Base64.encode import android.util.Base64.encodeToString +import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -33,108 +34,124 @@ import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream import javax.crypto.spec.OAEPParameterSpec import javax.crypto.spec.PSource.PSpecified +import javax.inject.Inject +import javax.inject.Singleton import javax.security.auth.x500.X500Principal -private const val KEYSTORE_NAME = "AndroidKeyStore" +@Singleton +class Scrambler @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val keyCharset = Charset.forName("UTF-8") -private const val KEY_ALIAS = "wulkanowy_password" + private val isKeyPairExists: Boolean + get() = keyStore.getKey(KEY_ALIAS, null) != null -private val KEY_CHARSET = Charset.forName("UTF-8") + private val keyStore: KeyStore + get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } -private val isKeyPairExists: Boolean - get() = keyStore.getKey(KEY_ALIAS, null) != null + private val cipher: Cipher + get() { + return if (SDK_INT >= M) Cipher.getInstance( + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + "AndroidKeyStoreBCWorkaround" + ) + else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL") + } -private val keyStore: KeyStore - get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } + fun encrypt(plainText: String): String { + if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") -private val cipher: Cipher - get() { - return if (SDK_INT >= M) Cipher.getInstance( - "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", - "AndroidKeyStoreBCWorkaround" - ) - else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL") + return try { + if (!isKeyPairExists) generateKeyPair() + + cipher.let { + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec) + } + } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) + + ByteArrayOutputStream().let { output -> + CipherOutputStream(output, it).apply { + write(plainText.toByteArray(keyCharset)) + close() + } + encodeToString(output.toByteArray(), DEFAULT) + } + } + } catch (exception: Exception) { + Timber.e(exception, "An error occurred while encrypting text") + String(encode(plainText.toByteArray(keyCharset), DEFAULT), keyCharset) + } } -fun encrypt(plainText: String, context: Context): String { - if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") + fun decrypt(cipherText: String): String { + if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - return try { - if (!isKeyPairExists) generateKeyPair(context) + return try { + if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") - cipher.let { - if (SDK_INT >= M) { - OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> - it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec) + cipher.let { + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec) + } + } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null)) + + CipherInputStream( + ByteArrayInputStream(decode(cipherText, DEFAULT)), + it + ).let { input -> + val values = ArrayList() + var nextByte: Int + while (run { nextByte = input.read(); nextByte } != -1) { + values.add(nextByte.toByte()) + } + val bytes = ByteArray(values.size) + for (i in bytes.indices) { + bytes[i] = values[i] + } + String(bytes, 0, bytes.size, keyCharset) } - } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) + } + } catch (e: Exception) { + throw ScramblerException("An error occurred while decrypting text", e) + } + } - ByteArrayOutputStream().let { output -> - CipherOutputStream(output, it).apply { - write(plainText.toByteArray(KEY_CHARSET)) - close() - } - encodeToString(output.toByteArray(), DEFAULT) + private fun generateKeyPair() { + (if (SDK_INT >= M) { + KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) + .setDigests(DIGEST_SHA256, DIGEST_SHA512) + .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP) + .setCertificateSerialNumber(BigInteger.TEN) + .setCertificateSubject(X500Principal("CN=Wulkanowy")) + .build() + } else { + KeyPairGeneratorSpec.Builder(context) + .setAlias(KEY_ALIAS) + .setSubject(X500Principal("CN=Wulkanowy")) + .setSerialNumber(BigInteger.TEN) + .setStartDate(Calendar.getInstance().time) + .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) + .build() + }).let { + KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply { + initialize(it) + genKeyPair() } } - } catch (exception: Exception) { - Timber.e(exception, "An error occurred while encrypting text") - String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + Timber.i("A new KeyPair has been generated") + } + + fun clearKeyPair() { + keyStore.deleteEntry(KEY_ALIAS) + Timber.i("KeyPair has been cleared") + } + + private companion object { + private const val KEYSTORE_NAME = "AndroidKeyStore" + private const val KEY_ALIAS = "wulkanowy_password" } } - -fun decrypt(cipherText: String): String { - if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - - return try { - if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") - - cipher.let { - if (SDK_INT >= M) { - OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> - it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec) - } - } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null)) - - CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input -> - val values = ArrayList() - var nextByte: Int - while (run { nextByte = input.read(); nextByte } != -1) { - values.add(nextByte.toByte()) - } - val bytes = ByteArray(values.size) - for (i in bytes.indices) { - bytes[i] = values[i] - } - String(bytes, 0, bytes.size, KEY_CHARSET) - } - } - } catch (e: Exception) { - throw ScramblerException("An error occurred while decrypting text", e) - } -} - -private fun generateKeyPair(context: Context) { - (if (SDK_INT >= M) { - KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) - .setDigests(DIGEST_SHA256, DIGEST_SHA512) - .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP) - .setCertificateSerialNumber(BigInteger.TEN) - .setCertificateSubject(X500Principal("CN=Wulkanowy")) - .build() - } else { - KeyPairGeneratorSpec.Builder(context) - .setAlias(KEY_ALIAS) - .setSubject(X500Principal("CN=Wulkanowy")) - .setSerialNumber(BigInteger.TEN) - .setStartDate(Calendar.getInstance().time) - .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) - .build() - }).let { - KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply { - initialize(it) - genKeyPair() - } - } - Timber.i("A new KeyPair has been generated") -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27c454adb..72910b85c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,8 @@ Log in Session expired Session expired, log in again + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Application support Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time Enable ads diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index ec6027e98..d7d83e6c9 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -101,8 +101,12 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { (activity as? BaseActivity<*, *>)?.showMessage(text) } - override fun showExpiredDialog() { - (activity as? BaseActivity<*, *>)?.showExpiredDialog() + override fun showExpiredCredentialsDialog() { + (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() + } + + override fun showDecryptionFailedDialog() { + (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showChangePasswordSnackbar(redirectUrl: String) { From 81e80181f264b375470777b22e1047968f8aceda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 8 Jan 2024 16:32:06 +0100 Subject: [PATCH 358/545] New Crowdin updates (#2388) --- app/src/main/res/values-cs/strings.xml | 4 +++- app/src/main/res/values-da-rDK/strings.xml | 2 ++ app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values-es-rES/strings.xml | 2 ++ app/src/main/res/values-it-rIT/strings.xml | 2 ++ app/src/main/res/values-pl/strings.xml | 2 ++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values-sk/strings.xml | 4 +++- app/src/main/res/values-uk/strings.xml | 2 ++ 9 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3f0940b58..8e60b7a65 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -96,6 +96,8 @@ Přihlásit se Relace vypršela Relace vypršela. Přihlaste se prosím znovu + Heslo k vašemu účtu bylo změněno. Musíte se znovu přihlásit do Wulkanového + Heslo bylo změněno Podpora aplikace Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout Zapnout reklamy @@ -760,7 +762,7 @@ Podpora Ochrana osobních údajů Souhlasy - Show consent to data processing + Zobrazit souhlas se zpracováním údajů Zobrazit reklamy v aplikaci Podívejte se na jednu reklamu pro podporu projektu Souhlas se zpracováním dat diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 512750630..013066629 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -96,6 +96,8 @@ Log in Session expired Session expired, log in again + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Application support Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time Enable ads diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index bfc194c03..09173d38b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -96,6 +96,8 @@ Anmelden Die Sitzung ist abgelaufen Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Anwendungsunterstützung Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können Werbung aktivieren diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 512750630..013066629 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -96,6 +96,8 @@ Log in Session expired Session expired, log in again + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Application support Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time Enable ads diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 512750630..013066629 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -96,6 +96,8 @@ Log in Session expired Session expired, log in again + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Application support Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time Enable ads diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2872e28e4..fb9d170a3 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -96,6 +96,8 @@ Zaloguj się Sesja wygasła Sesja wygasła, zaloguj się ponownie + Hasło do Twojego konta zostało zmienione. Musisz zalogować się ponownie do Wulkanowego + Hasło zostało zmienione Wparcie aplikacji Podoba Ci się ta aplikacja? Wspieraj jej rozwój poprzez włączenie nieinwazyjnych reklam, które możesz wyłączyć w dowolnym momencie Włącz reklamy diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 592e9ee8a..c604cd8b3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -96,6 +96,8 @@ Войти Сеанс истёк Сеанс истёк, авторизуйтесь снова + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Поддержка приложения Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время Включить рекламу diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index c9ad645e3..e02b1542a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -96,6 +96,8 @@ Prihlásiť sa Relácia vypršala Relácia vypršala. Prihláste sa prosím znovu + Heslo k vášmu účtu bolo zmenené. Musíte sa znovu prihlásiť do Wulkanového + Heslo bolo zmenené Podpora aplikácie Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť Zapnúť reklamy @@ -760,7 +762,7 @@ Podpora Ochrana osobných údajov Súhlasy - Show consent to data processing + Zobraziť súhlas so spracovaním údajov Zobraziť reklamy v aplikácii Pozrite sa na jednu reklamu pre podporu projektu Súhlas so spracovaním dát diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 86ee0910b..32617f429 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -96,6 +96,8 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову + Your account password has been changed. You need to log in to Wulkanowy again + Password changed Підтримка додатку Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час Увімкнути рекламу From 5316e3e1bf04291bcd0b5b7e14e0af010018a1a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:32:30 +0000 Subject: [PATCH 359/545] Bump mockk from 1.13.8 to 1.13.9 (#2389) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index acfc1bdcf..70b542356 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,7 +188,7 @@ ext { android_hilt = "1.1.0" room = "2.6.1" chucker = "4.0.0" - mockk = "1.13.8" + mockk = "1.13.9" coroutines = "1.7.3" } From 8324a9cac3f6a22ba3c8733618b7e85addf66c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 9 Jan 2024 19:00:37 +0100 Subject: [PATCH 360/545] Use emptyCookieJarInterceptor in SDK configuration (#2390) --- app/build.gradle | 2 +- app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 70b542356..2281848b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'com.github.wulkanowy:sdk:2.3.3' + implementation 'io.github.wulkanowy:sdk:2.3.4-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 889d64ea1..df99be98b 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -11,6 +11,7 @@ fun Sdk.init(student: Student): Sdk { schoolSymbol = student.schoolSymbol studentId = student.studentId classId = student.classId + emptyCookieJarInterceptor = true if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { mobileBaseUrl = student.mobileBaseUrl From 9dee7f01f63d853ae24f467bcd018723e8ff1980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 9 Jan 2024 19:07:46 +0100 Subject: [PATCH 361/545] Avoid deleting luckynumber when SDK returns null (#2391) --- .../data/repositories/LuckyNumberRepository.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 87e8410f1..4ff4517d0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -35,12 +35,15 @@ class LuckyNumberRepository @Inject constructor( fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) }, - saveFetchResult = { old, new -> - if (new != old) { - old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) } - luckyNumberDb.insertAll(listOfNotNull((new?.apply { - if (notify) isNotified = false - }))) + saveFetchResult = { oldLuckyNumber, newLuckyNumber -> + newLuckyNumber ?: return@networkBoundResource + + if (newLuckyNumber != oldLuckyNumber) { + val updatedLuckNumberList = + listOf(newLuckyNumber.apply { if (notify) isNotified = false }) + + oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) } + luckyNumberDb.insertAll(updatedLuckNumberList) } } ) From cff08d63221bf353cb91d069e1a05ab722dab253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 9 Jan 2024 19:27:03 +0100 Subject: [PATCH 362/545] Version 2.3.2 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2281848b8..dba4dc2f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 141 - versionName "2.3.1" + versionCode 142 + versionName "2.3.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.4-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.3.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 2fd7dbee1..46fac4acf 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,5 @@ -Wersja 2.3.1 +Wersja 2.3.2 -— poprawiliśmy kilka usterek przy odświeżaniu danych (ale pewnie nie wszystkie) +— poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From ddbcc7a04c28617765739d2b5774812f235ed611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 9 Jan 2024 21:45:59 +0100 Subject: [PATCH 363/545] Version 2.3.3 --- 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 dba4dc2f2..180df1a6a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 142 - versionName "2.3.2" + versionCode 143 + versionName "2.3.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.4' + implementation 'io.github.wulkanowy:sdk:2.3.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 46fac4acf..0a2eb68f4 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.3.2 +Wersja 2.3.3 — poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej) From a98e8398fd0a3783af729d6d61965f214c636f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 12 Jan 2024 18:34:43 +0100 Subject: [PATCH 364/545] Add webview to obtain cloudflare captcha cookies for okhttp (#2392) --- app/build.gradle | 3 +- .../io/github/wulkanowy/data/DataModule.kt | 2 + .../github/wulkanowy/ui/base/BaseActivity.kt | 5 ++ .../wulkanowy/ui/base/BaseDialogFragment.kt | 4 ++ .../github/wulkanowy/ui/base/BaseFragment.kt | 4 ++ .../github/wulkanowy/ui/base/BasePresenter.kt | 1 + .../io/github/wulkanowy/ui/base/BaseView.kt | 2 + .../github/wulkanowy/ui/base/ErrorHandler.kt | 4 ++ .../ui/modules/captcha/CaptchaDialog.kt | 72 +++++++++++++++++++ .../ui/modules/settings/SettingsFragment.kt | 3 + .../settings/advanced/AdvancedFragment.kt | 4 ++ .../settings/appearance/AppearanceFragment.kt | 4 ++ .../notifications/NotificationsFragment.kt | 4 ++ .../ui/modules/settings/sync/SyncFragment.kt | 4 ++ .../utils/WebkitCookieManagerProxy.kt | 39 ++++++++++ app/src/main/res/layout/dialog_captcha.xml | 12 ++++ .../ui/modules/settings/ads/AdsFragment.kt | 4 ++ 17 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt create mode 100644 app/src/main/res/layout/dialog_captcha.xml diff --git a/app/build.gradle b/app/build.gradle index 180df1a6a..7069672ad 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.5' + implementation 'io.github.wulkanowy:sdk:2.3.6-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' @@ -238,6 +238,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" + implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" implementation "com.jakewharton.timber:timber:5.0.1" implementation 'com.github.Faierbel:slf4j-timber:2.0' diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index bea3f7064..950e817bb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -21,6 +21,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.RemoteConfigHelper +import io.github.wulkanowy.utils.WebkitCookieManagerProxy import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -43,6 +44,7 @@ internal class DataModule { buildTag = android.os.Build.MODEL userAgentTemplate = remoteConfig.userAgentTemplate setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(WebkitCookieManagerProxy()) // for debug only addInterceptor(chuckerInterceptor, network = true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 026d38ded..29996db7c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.auth.AuthDialog +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor @@ -77,6 +78,10 @@ abstract class BaseActivity, VB : ViewBinding> : .show() } + override fun onCaptchaVerificationRequired(url: String?) { + CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog") + } + override fun showDecryptionFailedDialog() { MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 50e4b05d4..cb85fd8aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -32,6 +32,10 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index cec2670b2..4f919f456 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -43,6 +43,10 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index ee92e4fc1..d4cb20cac 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -29,6 +29,7 @@ open class BasePresenter( errorHandler.apply { showErrorMessage = view::showError onExpiredCredentials = view::showExpiredCredentialsDialog + onCaptchaVerificationRequired = view::onCaptchaVerificationRequired onDecryptionFailed = view::showDecryptionFailedDialog onNoCurrentStudent = view::openClearLoginView onPasswordChangeRequired = view::showChangePasswordSnackbar diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index e97a6ab90..88d5754f8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -8,6 +8,8 @@ interface BaseView { fun showExpiredCredentialsDialog() + fun onCaptchaVerificationRequired(url: String?) + fun showDecryptionFailedDialog() fun showAuthDialog() diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index 56905709d..e17c0c9ec 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException +import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.utils.getErrorString @@ -25,6 +26,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co var onAuthorizationRequired: () -> Unit = {} + var onCaptchaVerificationRequired: (url: String?) -> Unit = {} + fun dispatch(error: Throwable) { Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) @@ -38,6 +41,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co is BadCredentialsException -> onExpiredCredentials() is NoCurrentStudentException -> onNoCurrentStudent() is AuthorizationRequiredException -> onAuthorizationRequired() + is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt new file mode 100644 index 000000000..6c4d6420f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -0,0 +1,72 @@ +package io.github.wulkanowy.ui.modules.captcha + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.core.os.bundleOf +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.databinding.DialogCaptchaBinding +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.ui.base.BaseDialogFragment +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class CaptchaDialog : BaseDialogFragment() { + + @Inject + lateinit var sdk: Sdk + + companion object { + private const val CAPTCHA_URL = "captcha_url" + fun newInstance(url: String?): CaptchaDialog { + return CaptchaDialog().apply { + arguments = bundleOf(CAPTCHA_URL to url) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root + + @SuppressLint("SetJavaScriptEnabled") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding.captchaWebview) { + with(settings) { + javaScriptEnabled = true + userAgentString = sdk.userAgent + } + + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") { + if (it == "true") { + dismiss() + } else Timber.e("JS result: $it") + } + } + + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError? + ) { + super.onReceivedError(view, request, error) + } + } + + loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 19c4ef6b7..f8d1323c6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings import android.os.Bundle import androidx.preference.PreferenceFragmentCompat import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.main.MainView import timber.log.Timber @@ -26,6 +27,8 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin override fun showExpiredCredentialsDialog() {} + override fun onCaptchaVerificationRequired(url: String?) = Unit + override fun showDecryptionFailedDialog() {} override fun openClearLoginView() {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index 256b13375..a1d00227f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -51,6 +51,10 @@ class AdvancedFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 20423eb91..b9b35019a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -67,6 +67,10 @@ class AppearanceFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 2ae983c26..fdc4a24d9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -137,6 +137,10 @@ class NotificationsFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 133b1ff44..1e81e58ac 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -88,6 +88,10 @@ class SyncFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt new file mode 100644 index 000000000..509f39f58 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -0,0 +1,39 @@ +package io.github.wulkanowy.utils + +import java.net.CookiePolicy +import java.net.URI +import android.webkit.CookieManager as WebkitCookieManager +import java.net.CookieManager as JavaCookieManager + +class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { + + private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance() + + override fun put(uri: URI?, responseHeaders: Map>?) { + if (uri == null || responseHeaders == null) return + val url = uri.toString() + for (headerKey in responseHeaders.keys) { + if (headerKey == null || !( + headerKey.equals("Set-Cookie2", ignoreCase = true) || + headerKey.equals("Set-Cookie", ignoreCase = true) + ) + ) continue + + // process each of the headers + for (headerValue in responseHeaders[headerKey].orEmpty()) { + webkitCookieManager.setCookie(url, headerValue) + } + } + } + + override operator fun get( + uri: URI?, + requestHeaders: Map?>? + ): Map> { + require(!(uri == null || requestHeaders == null)) { "Argument is null" } + val res = mutableMapOf>() + val cookie = webkitCookieManager.getCookie(uri.toString()) + if (cookie != null) res["Cookie"] = listOf(cookie) + return res + } +} diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml new file mode 100644 index 000000000..2df18066d --- /dev/null +++ b/app/src/main/res/layout/dialog_captcha.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index d7d83e6c9..30b9e6b77 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -105,6 +105,10 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } From 096fe359e72abb67dc3cd7608428c5f91d29f334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jan 2024 14:09:04 +0100 Subject: [PATCH 365/545] Make some improvements in captcha dialog (#2393) * Add improvements retrying after captcha solved * Add showAuthDialog from BaseActivity instead of displaying this dialog manually * Add getCookieStore() with removeAll impl in WebkitCookieManagerProxy * Add debounce to captcha dialog showing logic * Add refresh button to captcha dialog * Destroy webview along with captcha dialog * Add clear webkit cookies button to debug menu * Add captcha error message * Update captcha verified message --- .../TimetableNotificationSchedulerHelper.kt | 2 - .../wulkanowy/ui/base/BaseDialogFragment.kt | 3 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 3 +- .../ui/modules/captcha/CaptchaDialog.kt | 40 ++++++++++++------ .../ui/modules/dashboard/DashboardFragment.kt | 12 ++++++ .../modules/dashboard/DashboardPresenter.kt | 8 ++++ .../ui/modules/dashboard/DashboardView.kt | 3 +- .../ui/modules/debug/DebugFragment.kt | 5 +++ .../ui/modules/debug/DebugPresenter.kt | 2 + .../wulkanowy/ui/modules/debug/DebugView.kt | 2 + .../modules/login/form/LoginFormFragment.kt | 9 ++++ .../modules/login/form/LoginFormPresenter.kt | 4 ++ .../wulkanowy/ui/modules/main/MainActivity.kt | 34 +++++++++++++++ .../settings/advanced/AdvancedFragment.kt | 3 +- .../settings/appearance/AppearanceFragment.kt | 3 +- .../notifications/NotificationsFragment.kt | 3 +- .../ui/modules/settings/sync/SyncFragment.kt | 3 +- .../wulkanowy/utils/ExceptionExtension.kt | 2 + .../utils/WebkitCookieManagerProxy.kt | 19 +++++++++ app/src/main/res/layout/dialog_captcha.xml | 41 ++++++++++++++++++- app/src/main/res/values/strings.xml | 7 ++++ 21 files changed, 179 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 42078d03f..aae7882f1 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor( range = lesson.start..lesson.end, requestCode = getRequestCode(lesson.start, studentId) ) - - Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index cb85fd8aa..e63887b8f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -8,7 +8,6 @@ import android.widget.Toast import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding import com.google.android.material.elevation.SurfaceColors -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.lifecycleAwareVariable import javax.inject.Inject @@ -49,7 +48,7 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun showErrorDetailsDialog(error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index 4f919f456..ba346131c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.utils.lifecycleAwareVariable abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), @@ -52,7 +51,7 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun openClearLoginView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt index 6c4d6420f..098d08ed9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -5,12 +5,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.webkit.WebResourceError -import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.core.os.bundleOf import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogCaptchaBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseDialogFragment @@ -23,8 +22,13 @@ class CaptchaDialog : BaseDialogFragment() { @Inject lateinit var sdk: Sdk + private var webView: WebView? = null + companion object { + const val CAPTCHA_SUCCESS = "captcha_success" private const val CAPTCHA_URL = "captcha_url" + private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null" + fun newInstance(url: String?): CaptchaDialog { return CaptchaDialog().apply { arguments = bundleOf(CAPTCHA_URL to url) @@ -41,8 +45,14 @@ class CaptchaDialog : BaseDialogFragment() { @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + isCancelable = false + binding.captchaRefresh.setOnClickListener { + binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) + } + binding.captchaClose.setOnClickListener { dismiss() } with(binding.captchaWebview) { + webView = this with(settings) { javaScriptEnabled = true userAgentString = sdk.userAgent @@ -50,23 +60,27 @@ class CaptchaDialog : BaseDialogFragment() { webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { - view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") { + view?.evaluateJavascript(CAPTCHA_CHECK_JS) { if (it == "true") { - dismiss() - } else Timber.e("JS result: $it") + onChallengeAccepted() + } } } - - override fun onReceivedError( - view: WebView?, - request: WebResourceRequest?, - error: WebResourceError? - ) { - super.onReceivedError(view, request, error) - } } loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) } } + + private fun onChallengeAccepted() { + runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) } + .onFailure { Timber.e(it) } + showMessage(getString(R.string.captcha_verified_message)) + dismiss() + } + + override fun onDestroy() { + webView?.destroy() + super.onDestroy() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index 301262a04..bedbce231 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter import io.github.wulkanowy.ui.modules.exam.ExamFragment @@ -36,6 +37,7 @@ import io.github.wulkanowy.utils.getErrorString import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.toFormattedString +import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -62,6 +64,9 @@ class DashboardFragment : BaseFragment(R.layout.fragme return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt() } + override val isViewEmpty + get() = dashboardAdapter.itemCount == 0 + companion object { fun newInstance() = DashboardFragment() @@ -77,6 +82,13 @@ class DashboardFragment : BaseFragment(R.layout.fragme super.onViewCreated(view, savedInstanceState) binding = FragmentDashboardBinding.bind(view) presenter.onAttachView(this) + initializeCaptchaResultObserver() + } + + private fun initializeCaptchaResultObserver() { + childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ -> + presenter.onRetryAfterCaptcha() + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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 c93dd9e78..d7add2c05 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 @@ -239,6 +239,14 @@ class DashboardPresenter @Inject constructor( loadData(selectedDashboardTiles, forceRefresh = true) } + fun onRetryAfterCaptcha() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(selectedDashboardTiles, forceRefresh = true) + } + fun onViewReselected() { Timber.i("Dashboard view is reselected") view?.run { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt index 767885434..fe011c929 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -6,6 +6,8 @@ interface DashboardView : BaseView { val tileWidth: Int + val isViewEmpty: Boolean + fun initView() fun updateData(data: List) @@ -27,6 +29,5 @@ interface DashboardView : BaseView { fun popViewToRoot() fun openNotificationsCenterView() - fun openInternetBrowser(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt index 000916b17..9db01a307 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug import android.os.Bundle import android.view.View +import android.webkit.CookieManager import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -58,6 +59,10 @@ class DebugFragment : BaseFragment(R.layout.fragment_debug (activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance()) } + override fun clearWebkitCookies() { + CookieManager.getInstance().removeAllCookies(null) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt index 67ac88861..816b59858 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt @@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor( val items = listOf( DebugItem(R.string.logviewer_title), DebugItem(R.string.notification_debug_title), + DebugItem(R.string.debug_cookies_clear), ) override fun onAttachView(view: DebugView) { @@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor( when (item.title) { R.string.logviewer_title -> view?.openLogViewer() R.string.notification_debug_title -> view?.openNotificationsDebug() + R.string.debug_cookies_clear -> view?.clearWebkitCookies() else -> Timber.d("Unknown debug item: $item") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt index 9396ec6ac..792d63d9e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt @@ -11,4 +11,6 @@ interface DebugView : BaseView { fun openLogViewer() fun openNotificationsDebug() + + fun clearWebkitCookies() } 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 8e9b86fa3..975cad185 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 @@ -7,6 +7,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.setFragmentResultListener import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.AdminMessage @@ -14,6 +15,7 @@ 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.captcha.CaptchaDialog 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 @@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment(R.layout.fragme super.onViewCreated(view, savedInstanceState) binding = FragmentLoginFormBinding.bind(view) presenter.onAttachView(this) + initializeCaptchaResultObserver() + } + + private fun initializeCaptchaResultObserver() { + setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ -> + presenter.onRetryAfterCaptcha() + } } override fun initView() { 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 ad535c382..c9ae4f27f 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 @@ -152,6 +152,10 @@ class LoginFormPresenter @Inject constructor( ) } + fun onRetryAfterCaptcha() { + onSignInClick() + } + fun onSignInClick() { val loginData = getLoginData() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index ba0ef4050..62c16257e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -30,6 +31,8 @@ import io.github.wulkanowy.databinding.ActivityMainBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog +import io.github.wulkanowy.ui.modules.auth.AuthDialog +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo @@ -40,10 +43,17 @@ import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.safelyPopFragments import io.github.wulkanowy.utils.setOnViewChangeListener +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @AndroidEntryPoint class MainActivity : BaseActivity(), MainView, @@ -73,6 +83,8 @@ class MainActivity : BaseActivity(), MainVie private val navController = FragNavController(supportFragmentManager, R.id.main_fragment_container) + private val captchaVerificationEvent = MutableSharedFlow() + companion object { private const val EXTRA_START_DESTINATION = "start_destination_json" @@ -144,6 +156,7 @@ class MainActivity : BaseActivity(), MainVie initializeToolbar() initializeBottomNavigation(startMenuIndex, rootAppMenuItems) initializeNavController(startMenuIndex, rootUpdatedDestinations) + initializeCaptchaVerificationEvent() } private fun initializeNavController( @@ -323,6 +336,27 @@ class MainActivity : BaseActivity(), MainVie .show() } + @OptIn(FlowPreview::class) + private fun initializeCaptchaVerificationEvent() { + captchaVerificationEvent + .debounce(1.seconds) + .onEach { url -> + Timber.d("Showing captcha dialog for: $url") + showDialogFragment(CaptchaDialog.newInstance(url)) + } + .launchIn(lifecycleScope) + } + + override fun onCaptchaVerificationRequired(url: String?) { + lifecycleScope.launch { + captchaVerificationEvent.emit(url) + } + } + + override fun showAuthDialog() { + showDialogFragment(AuthDialog.newInstance()) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index a1d00227f..3ef1a80a3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject @@ -72,7 +71,7 @@ class AdvancedFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index b9b35019a..3d0c8052b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -9,7 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import javax.inject.Inject @@ -88,7 +87,7 @@ class AppearanceFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index fdc4a24d9..0bf9ddadd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -21,7 +21,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.openInternetBrowser @@ -158,7 +157,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun showFixSyncDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 1e81e58ac..d57144832 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -10,7 +10,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import javax.inject.Inject @@ -109,7 +108,7 @@ class SyncFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt index a4c2537ac..18fc10bba 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.content.res.Resources import io.github.wulkanowy.R import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException @@ -34,6 +35,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) { is FeatureNotAvailableException -> R.string.error_feature_not_available is VulcanException -> R.string.error_unknown_uonet is ScrapperException -> R.string.error_unknown_app + is CloudflareVerificationException -> R.string.error_cloudflare_captcha is SSLHandshakeException -> when { error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime else -> R.string.error_timeout diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt index 509f39f58..a54978717 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.utils import java.net.CookiePolicy +import java.net.CookieStore +import java.net.HttpCookie import java.net.URI import android.webkit.CookieManager as WebkitCookieManager import java.net.CookieManager as JavaCookieManager @@ -36,4 +38,21 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL if (cookie != null) res["Cookie"] = listOf(cookie) return res } + + override fun getCookieStore(): CookieStore { + val cookies = super.getCookieStore() + return object : CookieStore { + override fun add(uri: URI?, cookie: HttpCookie?) = cookies.add(uri, cookie) + override fun get(uri: URI?): List = cookies.get(uri) + override fun getCookies(): List = cookies.cookies + override fun getURIs(): List = cookies.urIs + override fun remove(uri: URI?, cookie: HttpCookie?): Boolean = + cookies.remove(uri, cookie) + + override fun removeAll(): Boolean { + webkitCookieManager.removeAllCookies(null) + return true + } + } + } } diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml index 2df18066d..539aa0cc9 100644 --- a/app/src/main/res/layout/dialog_captcha.xml +++ b/app/src/main/res/layout/dialog_captcha.xml @@ -1,12 +1,51 @@ + + + + + + + android:layout_height="match_parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/captcha_close" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72910b85c..60d85606d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -833,6 +834,11 @@ Skip for now + + Verification is in progress. Wait… + Verified successfully + + No internet connection An error occurred. Check your device clock @@ -842,6 +848,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API From 9ececeb4e92b3adb2ac801097159d1bedd1a2de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jan 2024 16:41:57 +0100 Subject: [PATCH 366/545] New Crowdin updates (#2394) --- app/src/main/res/values-cs/strings.xml | 5 +++++ app/src/main/res/values-da-rDK/strings.xml | 5 +++++ app/src/main/res/values-de/strings.xml | 5 +++++ app/src/main/res/values-es-rES/strings.xml | 5 +++++ app/src/main/res/values-it-rIT/strings.xml | 5 +++++ app/src/main/res/values-pl/strings.xml | 5 +++++ app/src/main/res/values-ru/strings.xml | 5 +++++ app/src/main/res/values-sk/strings.xml | 5 +++++ app/src/main/res/values-uk/strings.xml | 5 +++++ 9 files changed, 45 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8e60b7a65..b4f1f878a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -13,6 +13,7 @@ Prohlížeč protokolů Ladění Ladění oznámení + Vymazat soubory cookie webview Tvůrci Licence Zprávy @@ -833,6 +834,9 @@ 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 + + Probíhá ověřování. Počkejte… + Úspěšně ověřeno Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení @@ -842,6 +846,7 @@ Probíhá údržba deníku UONET+. Zkuste to později znovu Neznámá chyba deniku UONET+. Prosím zkuste to znovu později Neznámá chyba aplikace. Prosím zkuste to znovu později + Vyžadováno ověření Captcha Vyskytla se neočekávaná chyba Funkce je deaktivována přes vaší školou Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 013066629..ac616418c 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ 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 + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 09173d38b..ec6aa655f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,6 +13,7 @@ Log Viewer Debuggen Benachrichtigungen debuggen + Clear webview cookies Mitarbeiter Lizenzen Nachrichten @@ -743,6 +744,9 @@ 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 + + Verification is in progress. Wait… + Verified successfully Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr @@ -752,6 +756,7 @@ Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal + Captcha verification required Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 013066629..ac616418c 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ 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 + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 013066629..ac616418c 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ 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 + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index fb9d170a3..1b4fbe664 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -13,6 +13,7 @@ Przeglądarka logów Debugowanie Debugowanie powiadomień + Wyczyść ciasteczka webview Twórcy Licencje Wiadomości @@ -833,6 +834,9 @@ Autoryzacja Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej Na razie pomiń + + Trwa weryfikacja. Czekaj… + Pomyślnie zweryfikowano Brak połączenia z internetem Wystąpił błąd. Sprawdź poprawność daty w urządzeniu @@ -842,6 +846,7 @@ Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później Nieznany błąd dziennika UONET+. Spróbuj ponownie później Nieznany błąd aplikacji. Spróbuj ponownie później + Wymagana weryfikacja captcha Wystąpił nieoczekiwany błąd Funkcja wyłączona przez szkołę Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c604cd8b3..feb08a03b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -13,6 +13,7 @@ Просмотр журнала Отладка Отладка уведомлений + Clear webview cookies Разработчики Лицензии Сообщения @@ -833,6 +834,9 @@ Авторизация Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже Пропустить сейчас + + Verification is in progress. Wait… + Verified successfully Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве @@ -842,6 +846,7 @@ UONET+ проводит техническое обслуживание, повторите попытку позже Неизвестная ошибка дневника UONET+, повторите попытку позже Неизвестная ошибка приложения, повторите попытку позже + Captcha verification required Произошла непредвиденная ошибка Функция отключена вашей школой Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e02b1542a..aaf04bc85 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -13,6 +13,7 @@ Prehliadač protokolov Ladenie Ladenie oznámení + Vymazať súbory cookie webview Tvorcovia Licencie Správy @@ -833,6 +834,9 @@ 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ť + + Overovanie prebieha. Počkajte… + Úspešne overené Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia @@ -842,6 +846,7 @@ Prebieha údržba denníka UONET+. Skúste to neskôr znova Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr Neznáma chyba aplikácie. Prosím skúste to znova neskôr + Vyžaduje sa overenie Captcha Vyskytla sa neočakávaná chyba Funkcia je deaktivovaná cez vašou školou Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 32617f429..fffae003b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -13,6 +13,7 @@ Переглядач логів Відладка Відладка сповіщень + Очистити кукі веб - перегляду Розробники Ліцензії Листи @@ -833,6 +834,9 @@ Авторизувати Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче Поки що пропустити + + Верифікація в процесі. Чекайте… + Верифікація завершена Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою @@ -842,6 +846,7 @@ UONET+ проводить технічне осблуговування, спробуйте пізніше Невідома помилка щоденника UONET+, спробуйте пізніше Невідома помилка програми, спробуйте пізніше + Необхідна перевірка Captcha Відбулася несподівана помилка Функція вимкнена вашою школою Функція недоступна в режимі Mobile API. Увійдіть в інший режим From 976eb5a7720fdc2e74356015016f19783ca53e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jan 2024 16:45:30 +0100 Subject: [PATCH 367/545] Fix cancelling dashboard jobs (#2395) --- .../java/io/github/wulkanowy/data/Resource.kt | 16 ++++++++-- .../ui/modules/captcha/CaptchaDialog.kt | 2 +- .../modules/dashboard/DashboardPresenter.kt | 30 ++++++++++++++++--- 3 files changed, 40 insertions(+), 8 deletions(-) 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 2c5bf0ea9..d7c2aeed9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -1,6 +1,16 @@ package io.github.wulkanowy.data -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber @@ -131,7 +141,7 @@ inline fun networkBoundResource( query().map { Resource.Success(filterResult(it)) } } catch (throwable: Throwable) { onFetchFailed(throwable) - query().map { Resource.Error(throwable) } + flowOf(Resource.Error(throwable)) } } else { query().map { Resource.Success(filterResult(it)) } @@ -165,7 +175,7 @@ inline fun networkBoundResource( query().map { Resource.Success(mapResult(it)) } } catch (throwable: Throwable) { onFetchFailed(throwable) - query().map { Resource.Error(throwable) } + flowOf(Resource.Error(throwable)) } } else { query().map { Resource.Success(mapResult(it)) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt index 098d08ed9..ed8293a9f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -76,7 +76,7 @@ class CaptchaDialog : BaseDialogFragment() { runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) } .onFailure { Timber.e(it) } showMessage(getString(R.string.captcha_verified_message)) - dismiss() + dismissAllowingStateLoss() } override fun onDestroy() { 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 d7add2c05..74b427e78 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 @@ -324,7 +324,7 @@ class DashboardPresenter @Inject constructor( ) { luckyNumberResource, messageResource, attendanceResource -> val resList = listOf(luckyNumberResource, messageResource, attendanceResource) - DashboardItem.HorizontalGroup( + resList to DashboardItem.HorizontalGroup( isLoading = resList.any { it is Resource.Loading }, error = resList.map { it.errorOrNull }.let { errors -> if (errors.all { it != null }) { @@ -349,9 +349,9 @@ class DashboardPresenter @Inject constructor( ) }) } - .filterNot { it.isLoading && forceRefresh } + .filterNot { (_, it) -> it.isLoading && forceRefresh } .distinctUntilChanged() - .onEach { + .onEach { (_, it) -> updateData(it, forceRefresh) if (it.isLoading) { @@ -369,7 +369,7 @@ class DashboardPresenter @Inject constructor( ) errorHandler.dispatch(it) } - .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}") + .launchWithUniqueRefreshJob("horizontal_group", forceRefresh) } private fun loadGrades(student: Student, forceRefresh: Boolean) { @@ -862,6 +862,28 @@ class DashboardPresenter @Inject constructor( onEach { if (it is Resource.Success) { cancelJobs(jobName) + } else if (it is Resource.Error) { + cancelJobs(jobName) + } + }.launch(jobName) + } else { + launch(jobName) + } + } + + @JvmName("launchWithUniqueRefreshJobHorizontalGroup") + private fun Flow>, *>>.launchWithUniqueRefreshJob( + name: String, + forceRefresh: Boolean + ) { + val jobName = if (forceRefresh) "$name-forceRefresh" else name + + if (forceRefresh) { + onEach { (resources, _) -> + if (resources.all { it is Resource.Success<*> }) { + cancelJobs(jobName) + } else if (resources.any { it is Resource.Error<*> }) { + cancelJobs(jobName) } }.launch(jobName) } else { From 497acf9d685102064b670a07038ecae6b2f47098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 14 Jan 2024 17:32:41 +0100 Subject: [PATCH 368/545] Version 2.3.4 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7069672ad..8d10ce926 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 143 - versionName "2.3.3" + versionCode 144 + versionName "2.3.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -163,7 +163,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.99d - updatePriority = 3 + updatePriority = 1 enabled.set(false) } @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.6-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.3.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 0a2eb68f4..c2c30883e 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.3.3 +Wersja 2.3.4 -— poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej) +— dodaliśmy obsługę captchy, co umożliwi używanie apki np. na odmianie ResMan Rzeszów +— naprawiliśmy wyświetlanie frekwencji w szkołach używających eduOne (piszcie, jeśli nadal nie działa) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From e58c15596109075691dfcb7a7cee974ff037d1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 14 Jan 2024 18:02:15 +0100 Subject: [PATCH 369/545] New Crowdin updates (#2396) --- app/src/main/res/values-da-rDK/strings.xml | 764 --------------------- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 764 --------------------- app/src/main/res/values-it-rIT/strings.xml | 764 --------------------- app/src/main/res/values-ru/strings.xml | 2 +- 5 files changed, 2 insertions(+), 2294 deletions(-) delete mode 100644 app/src/main/res/values-da-rDK/strings.xml delete mode 100644 app/src/main/res/values-es-rES/strings.xml delete mode 100644 app/src/main/res/values-it-rIT/strings.xml diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml deleted file mode 100644 index ac616418c..000000000 --- a/app/src/main/res/values-da-rDK/strings.xml +++ /dev/null @@ -1,764 +0,0 @@ - - - - Login - Wulkanowy - Grades - Attendance - Exams - Timetable - Settings - More - About - Log viewer - Debug - Notification debug - Clear webview cookies - 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 - E.g. \"lodz\" or \"powiatjaroslawski\" - 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. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school - 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! - Reset password - Recover your account - Recover - Student is already signed in - Standard - Other search locations - No active students found - Enter a different symbol - Get help - Full school name with the town (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Enter correct name of the school - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit - - 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 - Your account password has been changed. You need to log in to Wulkanowy again - Password changed - 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 - - No lesson - No lessons - - - 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 - Incognito mode is on - Thanks to incognito mode sender is not notified when you read the message - - 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 - 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 - 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 - Incognito mode - Do not inform about reading the message - Support - Privacy Policy - Agreements - Show consent to data processing - 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 - 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 - - Verification is in progress. Wait… - Verified successfully - - 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 - Captcha verification required - 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-de/strings.xml b/app/src/main/res/values-de/strings.xml index ec6aa655f..7e0ce8689 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -10,7 +10,7 @@ Einstellungen Mehr Über die Applikation - Log Viewer + Log viewer Debuggen Benachrichtigungen debuggen Clear webview cookies diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml deleted file mode 100644 index ac616418c..000000000 --- a/app/src/main/res/values-es-rES/strings.xml +++ /dev/null @@ -1,764 +0,0 @@ - - - - Login - Wulkanowy - Grades - Attendance - Exams - Timetable - Settings - More - About - Log viewer - Debug - Notification debug - Clear webview cookies - 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 - E.g. \"lodz\" or \"powiatjaroslawski\" - 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. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school - 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! - Reset password - Recover your account - Recover - Student is already signed in - Standard - Other search locations - No active students found - Enter a different symbol - Get help - Full school name with the town (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Enter correct name of the school - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit - - 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 - Your account password has been changed. You need to log in to Wulkanowy again - Password changed - 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 - - No lesson - No lessons - - - 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 - Incognito mode is on - Thanks to incognito mode sender is not notified when you read the message - - 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 - 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 - 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 - Incognito mode - Do not inform about reading the message - Support - Privacy Policy - Agreements - Show consent to data processing - 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 - 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 - - Verification is in progress. Wait… - Verified successfully - - 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 - Captcha verification required - 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-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml deleted file mode 100644 index ac616418c..000000000 --- a/app/src/main/res/values-it-rIT/strings.xml +++ /dev/null @@ -1,764 +0,0 @@ - - - - Login - Wulkanowy - Grades - Attendance - Exams - Timetable - Settings - More - About - Log viewer - Debug - Notification debug - Clear webview cookies - 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 - E.g. \"lodz\" or \"powiatjaroslawski\" - 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. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school - 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! - Reset password - Recover your account - Recover - Student is already signed in - Standard - Other search locations - No active students found - Enter a different symbol - Get help - Full school name with the town (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Enter correct name of the school - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit - - 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 - Your account password has been changed. You need to log in to Wulkanowy again - Password changed - 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 - - No lesson - No lessons - - - 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 - Incognito mode is on - Thanks to incognito mode sender is not notified when you read the message - - 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 - 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 - 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 - Incognito mode - Do not inform about reading the message - Support - Privacy Policy - Agreements - Show consent to data processing - 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 - 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 - - Verification is in progress. Wait… - Verified successfully - - 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 - Captcha verification required - 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-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index feb08a03b..2ca669287 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -829,7 +829,7 @@ Авторизация отклонена. Предоставленные данные не соответствуют записям в кабинете секретаря. Неправильный номер PESEL Номер PESEL - Authorize + Авторизовать Авторизация прошла успешно Авторизация Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже From 725668f855eb0ea41217e1a57d2b72547634ceed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:22:34 +0000 Subject: [PATCH 370/545] Bump androidx.lifecycle:lifecycle-livedata-ktx from 2.6.2 to 2.7.0 (#2398) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8d10ce926..cd8a1df20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -221,7 +221,7 @@ dependencies { implementation "androidx.work:work-runtime:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0" implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-ktx:$room" From 9dfb282e881aa0dc6278777993352740d84b43f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 21 Jan 2024 11:39:55 +0100 Subject: [PATCH 371/545] Add X to close admin message (#2401) --- .../58.json | 2451 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 124 +- .../data/db/entities/AdminMessage.kt | 7 +- .../data/db/migrations/Migration58.kt | 10 + .../viewholders/AdminMessageViewHolder.kt | 10 +- app/src/main/res/drawable/ic_close.xml | 10 + .../layout/item_dashboard_admin_message.xml | 15 +- 7 files changed, 2617 insertions(+), 10 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt create mode 100644 app/src/main/res/drawable/ic_close.xml diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json new file mode 100644 index 000000000..e6e71229c --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/58.json @@ -0,0 +1,2451 @@ +{ + "formatVersion": 1, + "database": { + "version": 58, + "identityHash": "cd1d4f8f2b6e3860fbc1de93d4f5ca42", + "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "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, 'cd1d4f8f2b6e3860fbc1de93d4f5ca42')" + ] + } +} \ 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 48a2942c9..a2f230f4c 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 @@ -1,11 +1,124 @@ package io.github.wulkanowy.data.db import android.content.Context -import androidx.room.* +import androidx.room.AutoMigration +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase import androidx.room.RoomDatabase.JournalMode.TRUNCATE -import io.github.wulkanowy.data.db.dao.* -import io.github.wulkanowy.data.db.entities.* -import io.github.wulkanowy.data.db.migrations.* +import androidx.room.TypeConverters +import io.github.wulkanowy.data.db.dao.AdminMessageDao +import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao +import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.db.dao.ConferenceDao +import io.github.wulkanowy.data.db.dao.ExamDao +import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.db.dao.HomeworkDao +import io.github.wulkanowy.data.db.dao.LuckyNumberDao +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.dao.MobileDeviceDao +import io.github.wulkanowy.data.db.dao.NoteDao +import io.github.wulkanowy.data.db.dao.NotificationDao +import io.github.wulkanowy.data.db.dao.RecipientDao +import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao +import io.github.wulkanowy.data.db.dao.SchoolDao +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.dao.StudentInfoDao +import io.github.wulkanowy.data.db.dao.SubjectDao +import io.github.wulkanowy.data.db.dao.TeacherDao +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao +import io.github.wulkanowy.data.db.dao.TimetableDao +import io.github.wulkanowy.data.db.dao.TimetableHeaderDao +import io.github.wulkanowy.data.db.entities.AdminMessage +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageAttachment +import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Notification +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.data.db.entities.SchoolAnnouncement +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentInfo +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader +import io.github.wulkanowy.data.db.migrations.Migration10 +import io.github.wulkanowy.data.db.migrations.Migration11 +import io.github.wulkanowy.data.db.migrations.Migration12 +import io.github.wulkanowy.data.db.migrations.Migration13 +import io.github.wulkanowy.data.db.migrations.Migration14 +import io.github.wulkanowy.data.db.migrations.Migration15 +import io.github.wulkanowy.data.db.migrations.Migration16 +import io.github.wulkanowy.data.db.migrations.Migration17 +import io.github.wulkanowy.data.db.migrations.Migration18 +import io.github.wulkanowy.data.db.migrations.Migration19 +import io.github.wulkanowy.data.db.migrations.Migration2 +import io.github.wulkanowy.data.db.migrations.Migration20 +import io.github.wulkanowy.data.db.migrations.Migration21 +import io.github.wulkanowy.data.db.migrations.Migration22 +import io.github.wulkanowy.data.db.migrations.Migration23 +import io.github.wulkanowy.data.db.migrations.Migration24 +import io.github.wulkanowy.data.db.migrations.Migration25 +import io.github.wulkanowy.data.db.migrations.Migration26 +import io.github.wulkanowy.data.db.migrations.Migration27 +import io.github.wulkanowy.data.db.migrations.Migration28 +import io.github.wulkanowy.data.db.migrations.Migration29 +import io.github.wulkanowy.data.db.migrations.Migration3 +import io.github.wulkanowy.data.db.migrations.Migration30 +import io.github.wulkanowy.data.db.migrations.Migration31 +import io.github.wulkanowy.data.db.migrations.Migration32 +import io.github.wulkanowy.data.db.migrations.Migration33 +import io.github.wulkanowy.data.db.migrations.Migration34 +import io.github.wulkanowy.data.db.migrations.Migration35 +import io.github.wulkanowy.data.db.migrations.Migration36 +import io.github.wulkanowy.data.db.migrations.Migration37 +import io.github.wulkanowy.data.db.migrations.Migration38 +import io.github.wulkanowy.data.db.migrations.Migration39 +import io.github.wulkanowy.data.db.migrations.Migration4 +import io.github.wulkanowy.data.db.migrations.Migration40 +import io.github.wulkanowy.data.db.migrations.Migration41 +import io.github.wulkanowy.data.db.migrations.Migration42 +import io.github.wulkanowy.data.db.migrations.Migration43 +import io.github.wulkanowy.data.db.migrations.Migration44 +import io.github.wulkanowy.data.db.migrations.Migration46 +import io.github.wulkanowy.data.db.migrations.Migration49 +import io.github.wulkanowy.data.db.migrations.Migration5 +import io.github.wulkanowy.data.db.migrations.Migration50 +import io.github.wulkanowy.data.db.migrations.Migration51 +import io.github.wulkanowy.data.db.migrations.Migration53 +import io.github.wulkanowy.data.db.migrations.Migration54 +import io.github.wulkanowy.data.db.migrations.Migration55 +import io.github.wulkanowy.data.db.migrations.Migration57 +import io.github.wulkanowy.data.db.migrations.Migration58 +import io.github.wulkanowy.data.db.migrations.Migration6 +import io.github.wulkanowy.data.db.migrations.Migration7 +import io.github.wulkanowy.data.db.migrations.Migration8 +import io.github.wulkanowy.data.db.migrations.Migration9 import io.github.wulkanowy.utils.AppInfo import javax.inject.Singleton @@ -51,6 +164,7 @@ import javax.inject.Singleton AutoMigration(from = 54, to = 55, spec = Migration55::class), AutoMigration(from = 55, to = 56), AutoMigration(from = 56, to = 57, spec = Migration57::class), + AutoMigration(from = 57, to = 58, spec = Migration58::class), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -59,7 +173,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 57 + const val VERSION_SCHEMA = 58 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), 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 875c2a3a5..0c8f1a5d1 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 @@ -37,6 +37,9 @@ data class AdminMessage( @ColumnInfo(name = "types", defaultValue = "[]") val types: List = emptyList(), - @ColumnInfo(name = "is_dismissible") - val isDismissible: Boolean = false + @ColumnInfo(name = "is_ok_visible", defaultValue = "0") + val isOkVisible: Boolean = false, + + @ColumnInfo(name = "is_x_visible", defaultValue = "0") + val isXVisible: Boolean = false ) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.kt new file mode 100644 index 000000000..c440d58d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration58.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 = "is_dismissible", +) +class Migration58 : AutoMigrationSpec diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt index 81099801a..1e0f0bdbf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/viewholders/AdminMessageViewHolder.kt @@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding -import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.utils.getThemeAttrColor class AdminMessageViewHolder( @@ -25,9 +24,11 @@ class AdminMessageViewHolder( 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) } @@ -37,11 +38,16 @@ class AdminMessageViewHolder( dashboardAdminMessageItemDescription.text = item.content dashboardAdminMessageItemDescription.setTextColor(textColor) dashboardAdminMessageItemIcon.setColorFilter(textColor) - dashboardAdminMessageItemDismiss.isVisible = item.isDismissible + dashboardAdminMessageItemDismiss.isVisible = item.isOkVisible + dashboardAdminMessageItemClose.isVisible = item.isXVisible dashboardAdminMessageItemDismiss.setTextColor(textColor) + dashboardAdminMessageItemClose.imageTintList = ColorStateList.valueOf(textColor) dashboardAdminMessageItemDismiss.setOnClickListener { onAdminMessageDismissClickListener(item) } + dashboardAdminMessageItemClose.setOnClickListener { + onAdminMessageDismissClickListener(item) + } root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) item.destinationUrl?.let { url -> diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000..1d6c00461 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/item_dashboard_admin_message.xml b/app/src/main/res/layout/item_dashboard_admin_message.xml index e12241df5..407e12921 100644 --- a/app/src/main/res/layout/item_dashboard_admin_message.xml +++ b/app/src/main/res/layout/item_dashboard_admin_message.xml @@ -34,11 +34,24 @@ android:layout_marginEnd="16dp" android:textSize="18sp" android:textStyle="bold" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/dashboard_admin_message_item_close" app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem" /> + + Date: Sun, 21 Jan 2024 12:39:23 +0100 Subject: [PATCH 372/545] Add admin message to error view in dashboard (#2400) --- app/build.gradle | 2 +- .../ui/modules/dashboard/DashboardFragment.kt | 13 +++++-- .../modules/dashboard/DashboardPresenter.kt | 20 ++++++----- .../ui/modules/dashboard/DashboardView.kt | 3 +- .../main/res/layout/fragment_dashboard.xml | 36 +++++++++++++++---- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cd8a1df20..a67adb6fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.6' + implementation 'io.github.wulkanowy:sdk:2.3.7-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index bedbce231..b7a0796c5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -21,6 +21,7 @@ import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragme import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter +import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment @@ -37,7 +38,6 @@ import io.github.wulkanowy.utils.getErrorString import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.toFormattedString -import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -199,8 +199,17 @@ class DashboardFragment : BaseFragment(R.layout.fragme binding.dashboardRecycler.isVisible = show } - override fun showErrorView(show: Boolean) { + override fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages?) { binding.dashboardErrorContainer.isVisible = show + binding.dashboardErrorAdminMessage.root.isVisible = adminMessageItem != null + + if (adminMessageItem != null) { + AdminMessageViewHolder( + binding = binding.dashboardErrorAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessageItem.adminMessage) + } } override fun setErrorDetails(error: Throwable) { 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 74b427e78..4e1587439 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 @@ -403,7 +403,7 @@ class DashboardPresenter @Inject constructor( subjectWithGrades = it.dataOrNull, gradeTheme = preferencesRepository.gradeColorTheme, isLoading = true - ), forceRefresh + ), false ) if (!it.dataOrNull.isNullOrEmpty()) { @@ -452,7 +452,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) return@onEach updateData( DashboardItem.Lessons(it.dataOrNull, isLoading = true), - forceRefresh + false ) if (!it.dataOrNull?.lessons.isNullOrEmpty()) { @@ -509,7 +509,7 @@ class DashboardPresenter @Inject constructor( val data = it.dataOrNull.orEmpty() updateData( DashboardItem.Homework(data, isLoading = true), - forceRefresh + false ) if (data.isNotEmpty()) { @@ -543,7 +543,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) return@onEach updateData( DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true), - forceRefresh + false ) if (!it.dataOrNull.isNullOrEmpty()) { @@ -586,7 +586,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) return@onEach updateData( DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true), - forceRefresh + false ) if (!it.dataOrNull.isNullOrEmpty()) { @@ -627,7 +627,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) return@onEach updateData( DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true), - forceRefresh + false ) if (!it.dataOrNull.isNullOrEmpty()) { @@ -662,7 +662,7 @@ class DashboardPresenter @Inject constructor( is Resource.Loading -> { Timber.i("Loading dashboard admin message data started") if (forceRefresh) return@onEach - updateData(DashboardItem.AdminMessages(), forceRefresh) + updateData(DashboardItem.AdminMessages(), false) } is Resource.Success -> { @@ -692,7 +692,7 @@ class DashboardPresenter @Inject constructor( private fun loadAds(forceRefresh: Boolean) { presenterScope.launch { if (!forceRefresh) { - updateData(DashboardItem.Ads(), forceRefresh) + updateData(DashboardItem.Ads(), false) } val dashboardAdItem = @@ -813,6 +813,8 @@ class DashboardPresenter @Inject constructor( val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE } + val dataLoadedAdminMessageItem = + itemsLoadedList.find { it.type == DashboardItem.Type.ADMIN_MESSAGE && it.isDataLoaded } as DashboardItem.AdminMessages? val isAccountItemError = itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null val isGeneralError = @@ -834,7 +836,7 @@ class DashboardPresenter @Inject constructor( showRefresh(false) if ((forceRefresh && wasGeneralError) || !forceRefresh) { showContent(false) - showErrorView(true) + showErrorView(true, dataLoadedAdminMessageItem) setErrorDetails(lastError) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt index fe011c929..56a0a773a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -20,7 +20,7 @@ interface DashboardView : BaseView { fun showRefresh(show: Boolean) - fun showErrorView(show: Boolean) + fun showErrorView(show: Boolean, adminMessageItem: DashboardItem.AdminMessages? = null) fun setErrorDetails(error: Throwable) @@ -29,5 +29,6 @@ interface DashboardView : BaseView { fun popViewToRoot() fun openNotificationsCenterView() + fun openInternetBrowser(url: String) } diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index 348602d77..9de44a70d 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -18,7 +18,8 @@ + android:layout_height="match_parent" + tools:visibility="gone"> - + + @@ -55,14 +70,21 @@ android:gravity="center" android:padding="8dp" android:text="@string/error_unknown" - android:textSize="20sp" /> + android:textSize="20sp" + app:layout_constraintBottom_toTopOf="@id/dashboard_error_buttons" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/dashboard_error_image" /> + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/dashboard_error_message"> - + From a51a54dc7ac444f73bb525c5a54c26d9c281ce46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Jan 2024 18:59:54 +0100 Subject: [PATCH 373/545] Normalize synchronization date ranges to fix weird notification issues (#2403) --- .../data/repositories/ExamRepository.kt | 18 ++++++++++-------- .../data/repositories/HomeworkRepository.kt | 7 ++++++- .../services/sync/works/AttendanceWork.kt | 13 ++++++++++--- .../wulkanowy/services/sync/works/ExamWork.kt | 16 +++++++++++++--- .../services/sync/works/HomeworkWork.kt | 16 +++++++++++++--- .../services/sync/works/TimetableWork.kt | 13 ++++++++++--- .../ui/modules/dashboard/DashboardPresenter.kt | 4 ++-- 7 files changed, 64 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 013c0951d..59f422428 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -67,14 +67,16 @@ class ExamRepository @Inject constructor( filterResult = { it.filter { item -> item.date in start..end } } ) - fun getExamsFromDatabase(semester: Semester, start: LocalDate): Flow> { - return examDb.loadAll( - diaryId = semester.diaryId, - studentId = semester.studentId, - from = start.startExamsDay, - end = start.endExamsDay - ) - } + fun getExamsFromDatabase( + semester: Semester, + start: LocalDate, + end: LocalDate + ): Flow> = examDb.loadAll( + diaryId = semester.diaryId, + studentId = semester.studentId, + from = start, + end = end, + ) suspend fun updateExam(exam: List) = examDb.updateAll(exam) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index f564824de..048c95a38 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -7,7 +7,12 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index 657f69638..4fc097492 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -16,17 +16,24 @@ class AttendanceWork @Inject constructor( ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().previousOrSameSchoolDay + val endDate = startDate.plusDays(7) + attendanceRepository.getAttendance( student = student, semester = semester, - start = now().previousOrSameSchoolDay, - end = now().previousOrSameSchoolDay, + start = startDate, + end = endDate, forceRefresh = true, notify = notify, ) .waitForResult() - attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now()) + attendanceRepository.getAttendanceFromDatabase( + semester = semester, + start = startDate, + end = endDate, + ) .first() .filterNot { it.isNotified } .let { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index 7071bce20..4b0b1bdb3 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewExamNotification +import io.github.wulkanowy.utils.endExamsDay +import io.github.wulkanowy.utils.startExamsDay import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject @@ -15,16 +17,24 @@ class ExamWork @Inject constructor( ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().startExamsDay + val endDate = startDate.endExamsDay + examRepository.getExams( student = student, semester = semester, - start = now(), - end = now(), + start = startDate, + end = endDate, forceRefresh = true, notify = notify, ).waitForResult() - examRepository.getExamsFromDatabase(semester, now()).first() + examRepository.getExamsFromDatabase( + semester = semester, + start = startDate, + end = endDate, + ) + .first() .filter { !it.isNotified }.let { if (it.isNotEmpty()) newExamNotification.notify(it, student) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index 4cfe27d0d..ddff3af7c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification +import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.sunday import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject @@ -16,16 +18,24 @@ class HomeworkWork @Inject constructor( ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().nextOrSameSchoolDay.monday + val endDate = startDate.sunday + homeworkRepository.getHomework( student = student, semester = semester, - start = now().nextOrSameSchoolDay, - end = now().nextOrSameSchoolDay, + start = startDate, + end = endDate, forceRefresh = true, notify = notify, ).waitForResult() - homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first() + homeworkRepository.getHomeworkFromDatabase( + semester = semester, + start = startDate, + end = endDate + ) + .first() .filter { !it.isNotified }.let { if (it.isNotEmpty()) newHomeworkNotification.notify(it, student) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 29b1f13c7..ac9a8eb4c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -16,17 +16,24 @@ class TimetableWork @Inject constructor( ) : Work { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { + val startDate = now().nextOrSameSchoolDay + val endDate = startDate.plusDays(7) + timetableRepository.getTimetable( student = student, semester = semester, - start = now().nextOrSameSchoolDay, - end = now().nextOrSameSchoolDay, + start = startDate, + end = endDate, forceRefresh = true, notify = notify, ) .waitForResult() - timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7)) + timetableRepository.getTimetableFromDatabase( + semester = semester, + from = startDate, + end = endDate, + ) .first() .filterNot { it.isNotified } .let { 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 4e1587439..ad604499b 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 @@ -435,13 +435,13 @@ class DashboardPresenter @Inject constructor( private fun loadLessons(student: Student, forceRefresh: Boolean) { flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) - val date = LocalDate.now() + val date = LocalDate.now().nextOrSameSchoolDay timetableRepository.getTimetable( student = student, semester = semester, start = date, - end = date.plusDays(1), + end = date, forceRefresh = forceRefresh ) } From e0f4cad7fb4e35cc38772b02e7b9a8a62de51996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Jan 2024 20:01:00 +0100 Subject: [PATCH 374/545] Add missing unitId to sdk switchSemester call (#2402) --- .../data/repositories/AttendanceRepository.kt | 12 +++++++++--- .../data/repositories/AttendanceSummaryRepository.kt | 3 ++- .../data/repositories/CompletedLessonsRepository.kt | 2 +- .../data/repositories/ConferenceRepository.kt | 3 ++- .../wulkanowy/data/repositories/ExamRepository.kt | 10 ++++++++-- .../wulkanowy/data/repositories/GradeRepository.kt | 2 +- .../data/repositories/GradeStatisticsRepository.kt | 7 ++++--- .../data/repositories/HomeworkRepository.kt | 2 +- .../data/repositories/MobileDeviceRepository.kt | 7 ++++--- .../wulkanowy/data/repositories/NoteRepository.kt | 2 +- .../wulkanowy/data/repositories/SchoolRepository.kt | 3 ++- .../wulkanowy/data/repositories/SchoolsRepository.kt | 7 ++----- .../data/repositories/StudentInfoRepository.kt | 3 ++- .../wulkanowy/data/repositories/StudentRepository.kt | 5 +++-- .../wulkanowy/data/repositories/SubjectRepository.kt | 6 ++++-- .../wulkanowy/data/repositories/TeacherRepository.kt | 3 ++- .../data/repositories/TimetableRepository.kt | 2 +- .../java/io/github/wulkanowy/utils/SdkExtension.kt | 10 ++++++++++ 18 files changed, 59 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 3afb99077..6d782047b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -9,7 +9,13 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -58,7 +64,7 @@ class AttendanceRepository @Inject constructor( ) } sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getAttendance(start.monday, end.sunday) .mapToEntities(semester, lessons) }, @@ -97,7 +103,7 @@ class AttendanceRepository @Inject constructor( ) } sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .excuseForAbsence(items, reason) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index 8e0709135..6bdcf9d7f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -40,7 +41,7 @@ class AttendanceSummaryRepository @Inject constructor( query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getAttendanceSummary(subjectId) .mapToEntities(semester, subjectId) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index 8f393cadb..1579ae62b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -48,7 +48,7 @@ class CompletedLessonsRepository @Inject constructor( }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getCompletedLessons(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index 83204cab0..7eb37f0b7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -10,6 +10,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -46,7 +47,7 @@ class ConferenceRepository @Inject constructor( }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getConferences() .mapToEntities(semester) .filter { it.date >= startDate } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 59f422428..96026a55b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -7,7 +7,13 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.endExamsDay +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.startExamsDay +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -51,7 +57,7 @@ class ExamRepository @Inject constructor( }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getExams(start.startExamsDay, start.endExamsDay) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index f5f895d82..b2bb7b906 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -52,7 +52,7 @@ class GradeRepository @Inject constructor( }, fetch = { val (details, summary) = sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getGrades(semester.semesterId) details.mapToEntities(semester) to summary.mapToEntities(semester) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 9fa06c497..23d7b8582 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.util.* @@ -56,7 +57,7 @@ class GradeStatisticsRepository @Inject constructor( query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getGradesPartialStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -101,7 +102,7 @@ class GradeStatisticsRepository @Inject constructor( query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getGradesSemesterStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -157,7 +158,7 @@ class GradeStatisticsRepository @Inject constructor( query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getGradesPointsStatistics(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 048c95a38..d4899b19e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -55,7 +55,7 @@ class HomeworkRepository @Inject constructor( }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getHomework(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 07c6959e3..412f9e7f0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -42,7 +43,7 @@ class MobileDeviceRepository @Inject constructor( query = { mobileDb.loadAll(student.userLoginId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getRegisteredDevices() .mapToEntities(student) }, @@ -56,7 +57,7 @@ class MobileDeviceRepository @Inject constructor( suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .unregisterDevice(device.deviceId) mobileDb.deleteAll(listOf(device)) @@ -64,7 +65,7 @@ class MobileDeviceRepository @Inject constructor( suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { return sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getToken() .mapToMobileDeviceToken() } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index 4101803f3..eeb1d53ef 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -41,7 +41,7 @@ class NoteRepository @Inject constructor( query = { noteDb.loadAll(student.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getNotes() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index 7972ed084..f757ef047 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -40,7 +41,7 @@ class SchoolRepository @Inject constructor( query = { schoolDb.load(semester.studentId, semester.classId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getSchool() .mapToEntity(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt index 9c6429343..216a8c112 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt @@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.IntegrityHelper import io.github.wulkanowy.utils.getCurrentOrLast import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withTimeout import timber.log.Timber import java.util.UUID @@ -42,11 +43,7 @@ class SchoolsRepository @Inject constructor( val schoolInfo = sdk .init(student.copy(password = loginData.password)) - .switchDiary( - diaryId = semester.diaryId, - kindergartenDiaryId = semester.kindergartenDiaryId, - schoolYear = semester.schoolYear - ) + .switchSemester(semester) .getSchool() schoolsService.logLoginEvent( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index efc82a772..d6cd25c82 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -30,7 +31,7 @@ class StudentInfoRepository @Inject constructor( query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getStudentInfo().mapToEntity(semester) }, saveFetchResult = { old, new -> 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 bfad12a8f..e063840cb 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 @@ -16,6 +16,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.security.Scrambler +import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -149,12 +150,12 @@ class StudentRepository @Inject constructor( suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .authorizePermission(pesel) suspend fun refreshStudentName(student: Student, semester: Semester) { val newCurrentApiStudent = sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getCurrentStudent() ?: return val studentName = StudentName( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index 3926122b3..98cb181af 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -39,8 +40,9 @@ class SubjectRepository @Inject constructor( query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) - .getSubjects().mapToEntities(semester) + .switchSemester(semester) + .getSubjects() + .mapToEntities(semester) }, saveFetchResult = { old, new -> subjectDao.deleteAll(old uniqueSubtract new) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 4e3b40f96..42698f922 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -39,7 +40,7 @@ class TeacherRepository @Inject constructor( query = { teacherDb.loadAll(semester.studentId, semester.classId) }, fetch = { sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getTeachers() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 136fb8d5b..9305d3b31 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -65,7 +65,7 @@ class TimetableRepository @Inject constructor( query = { getFullTimetableFromDatabase(student, semester, start, end) }, fetch = { val timetableFull = sdk.init(student) - .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) + .switchSemester(semester) .getTimetable(start.monday, end.sunday) timetableFull.mapToEntities(semester) 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 df99be98b..9b6ca7060 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.utils +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk import timber.log.Timber @@ -30,3 +31,12 @@ fun Sdk.init(student: Student): Sdk { return this } + +fun Sdk.switchSemester(semester: Semester): Sdk { + return switchDiary( + diaryId = semester.diaryId, + kindergartenDiaryId = semester.kindergartenDiaryId, + schoolYear = semester.schoolYear, + unitId = semester.unitId, + ) +} From dc59f4ffa35c85bea6db3d0eef81b1b2e484f94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Jan 2024 21:04:23 +0100 Subject: [PATCH 375/545] Version 2.3.5 --- app/build.gradle | 8 ++++---- .../wulkanowy/data/repositories/HomeworkRepository.kt | 1 + app/src/main/play/release-notes/pl-PL/default.txt | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a67adb6fa..7b290e638 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 144 - versionName "2.3.4" + versionCode 145 + versionName "2.3.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -162,7 +162,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.99d + userFraction = 0.15d updatePriority = 1 enabled.set(false) } @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.7-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.3.7' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index d4899b19e..010cf8458 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate 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 c2c30883e..04f3ba463 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,6 @@ -Wersja 2.3.4 +Wersja 2.3.5 -— dodaliśmy obsługę captchy, co umożliwi używanie apki np. na odmianie ResMan Rzeszów -— naprawiliśmy wyświetlanie frekwencji w szkołach używających eduOne (piszcie, jeśli nadal nie działa) +— naprawiliśmy ładowanie frekwencji dla szkół używających eduOne +— naprawiliśmy wielokrotne wysyłanie powiadomień o zmianach w planie lekcji Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 098af9884a4179e2ea44a22330b068b58ae3c2da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:17:29 +0000 Subject: [PATCH 376/545] Bump com.google.firebase:firebase-bom from 32.7.0 to 32.7.1 (#2404) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7b290e638..54659cdd7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.7.0') + playImplementation platform('com.google.firebase:firebase-bom:32.7.1') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From 10add8a70ed105a214579d6ee019dc770765f42c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:21:30 +0000 Subject: [PATCH 377/545] Bump com.android.tools.build:gradle from 8.2.1 to 8.2.2 (#2406) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 095d1b72f..e6e090b80 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.16" - classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.android.tools.build:gradle:8.2.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.0' classpath 'com.huawei.agconnect:agcp:1.9.1.303' From 88043569ac8cb7a6fe1b418ece97aaec1b29e6f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:23:01 +0000 Subject: [PATCH 378/545] Bump com.huawei.hms:hianalytics from 6.12.0.300 to 6.12.0.301 (#2407) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 54659cdd7..c40bef8eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -262,7 +262,7 @@ dependencies { playImplementation 'com.google.android.play:review-ktx:2.0.1' playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" - hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' + hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" From fc91936884715cc1d46d5af17086364961f8631d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:30:36 +0000 Subject: [PATCH 379/545] Bump com.google.android.ump:user-messaging-platform from 2.1.0 to 2.2.0 (#2408) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c40bef8eb..c0caf1dd8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -260,7 +260,7 @@ dependencies { playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' - playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" + playImplementation "com.google.android.ump:user-messaging-platform:2.2.0" hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' From e58a60410c3a6ee0dda506d2422905fe9cc8ee4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 4 Feb 2024 10:48:59 +0100 Subject: [PATCH 380/545] Fix android tests (#2410) --- .gitignore | 2 ++ .idea/migrations.xml | 10 ---------- app/build.gradle | 4 +++- .../wulkanowy/utils/security/ScramblerTest.kt | 15 +++++++++------ 4 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 .idea/migrations.xml diff --git a/.gitignore b/.gitignore index 921bd0a9a..980085e38 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,8 @@ captures/ .idea/uiDesigner.xml .idea/runConfigurations.xml .idea/discord.xml +.idea/migrations.xml +.idea/androidTestResultsUserPreferences.xml # Keystore files *.jks diff --git a/.idea/migrations.xml b/.idea/migrations.xml deleted file mode 100644 index f8051a6f9..000000000 --- a/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c0caf1dd8..b60dc3b4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,9 @@ android { packagingOptions { resources { excludes += ['META-INF/library_release.kotlin_module', - 'META-INF/library-core_release.kotlin_module'] + 'META-INF/library-core_release.kotlin_module', + 'META-INF/LICENSE.md', + 'META-INF/LICENSE-notice.md'] } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt index 0c47e6bb6..1b0319f69 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt @@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class ScramblerTest { + private val scrambler = Scrambler(ApplicationProvider.getApplicationContext()) + @Test fun encryptDecryptTest() { - assertEquals("TEST", decrypt(encrypt("TEST", - ApplicationProvider.getApplicationContext()))) + assertEquals( + "TEST", scrambler.decrypt(scrambler.encrypt("TEST")) + ) } @Test fun emptyTextEncryptTest() { assertFailsWith { - decrypt("") + scrambler.decrypt("") } assertFailsWith { - encrypt("", ApplicationProvider.getApplicationContext()) + scrambler.encrypt("") } } @Test @SdkSuppress(minSdkVersion = 18) fun emptyKeyStoreTest() { - val text = encrypt("test", ApplicationProvider.getApplicationContext()) + val text = scrambler.encrypt("test") val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) keyStore.deleteEntry("wulkanowy_password") assertFailsWith { - decrypt(text) + scrambler.decrypt(text) } } } From a05f1f70f7f8c7b7dbcc0aefa6f98cd021522943 Mon Sep 17 00:00:00 2001 From: JestemKamil <84380834+JestemKamil@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:21:56 +0100 Subject: [PATCH 381/545] Add colors to attendance and ! to unexcused lateness (#2412) --- .../modules/attendance/AttendanceAdapter.kt | 32 ++++++++++++++++++- .../ui/modules/attendance/AttendanceDialog.kt | 12 +++++++ app/src/main/res/values-night-v31/styles.xml | 3 ++ app/src/main/res/values-night/styles.xml | 2 ++ app/src/main/res/values-v31/styles.xml | 3 ++ app/src/main/res/values/attrs.xml | 2 ++ app/src/main/res/values/colors.xml | 6 ++++ app/src/main/res/values/styles.xml | 2 ++ 8 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 39f376f65..4e9baac3a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance +import android.graphics.Typeface import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.enums.SentExcuseStatus import io.github.wulkanowy.databinding.ItemAttendanceBinding import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.isExcusableOrNotExcused import javax.inject.Inject @@ -39,7 +41,33 @@ class AttendanceAdapter @Inject constructor() : root.context.getString(R.string.all_no_data) } attendanceItemDescription.setText(item.descriptionRes) - attendanceItemAlert.isVisible = item.let { it.absence && !it.excused } + + attendanceItemDescription.setTextColor( + root.context.getThemeAttrColor( + when { + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.textColorSecondary + } + ) + ) + + if (item.exemption || item.excused) { + attendanceItemDescription.setTypeface(null, Typeface.BOLD) + } else { + attendanceItemDescription.setTypeface(null, Typeface.NORMAL) + } + + attendanceItemAlert.isVisible = + item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) } + + attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor( + when{ + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.colorPrimary + } + )) attendanceItemNumber.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.GONE @@ -54,10 +82,12 @@ class AttendanceAdapter @Inject constructor() : attendanceItemExcuseInfo.visibility = View.VISIBLE attendanceItemAlert.visibility = View.INVISIBLE } + SentExcuseStatus.DENIED -> { attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied) attendanceItemExcuseInfo.visibility = View.VISIBLE } + else -> { if (item.isExcusableOrNotExcused && excuseActionMode) { attendanceItemNumber.visibility = View.GONE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index c0026bee5..635033132 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -6,10 +6,12 @@ import android.view.View import androidx.core.os.bundleOf import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogAttendanceBinding import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString @@ -44,6 +46,16 @@ class AttendanceDialog : BaseDialogFragment() { with(binding) { attendanceDialogSubjectValue.text = attendance.subject attendanceDialogDescriptionValue.setText(attendance.descriptionRes) + attendanceDialogDescriptionValue.setTextColor( + root.context.getThemeAttrColor( + when { + attendance.absence && !attendance.excused -> R.attr.colorAttendanceAbsence + attendance.lateness && !attendance.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.textColorSecondary + } + ) + ) + attendanceDialogDateValue.text = attendance.date.toFormattedString() attendanceDialogNumberValue.text = attendance.number.toString() attendanceDialogClose.setOnClickListener { dismiss() } diff --git a/app/src/main/res/values-night-v31/styles.xml b/app/src/main/res/values-night-v31/styles.xml index 6e6c4d79c..808bc714d 100644 --- a/app/src/main/res/values-night-v31/styles.xml +++ b/app/src/main/res/values-night-v31/styles.xml @@ -32,6 +32,9 @@ @color/material_dynamic_primary40 @color/timetable_canceled_dark @color/timetable_change_dark + @color/attendance_absence_dark + @color/attendance_lateness_dark + @color/colorErrorLight @color/colorDividerInverse @color/material_dynamic_secondary20 diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 5d9aa22a6..5840a051e 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -21,6 +21,8 @@ @color/colorSurfaceDark @color/timetable_canceled_dark @color/timetable_change_dark + @color/attendance_absence_dark + @color/attendance_lateness_dark @color/colorErrorLight @color/colorDividerInverse @color/colorSwipeRefreshDark diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml index bb47b22ed..358806681 100644 --- a/app/src/main/res/values-v31/styles.xml +++ b/app/src/main/res/values-v31/styles.xml @@ -34,6 +34,9 @@ @color/material_dynamic_primary80 @color/timetable_canceled_light @color/timetable_change_light + @color/attendance_absence_light + @color/attendance_lateness_light + @color/colorError @color/colorDivider @color/material_dynamic_secondary90 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index aa58fa09e..3c3fdb8e9 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -7,4 +7,6 @@ + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 87057c61d..8ad27ad88 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -49,6 +49,12 @@ #ff8f00 #ffd54f + #d32f2f + #e57373 + + #cd2a01 + #f05d0e + #1f000000 #1fffffff diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a0023dda1..b5b029501 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -19,6 +19,8 @@ @color/colorSurface @color/timetable_canceled_light @color/timetable_change_light + @color/attendance_absence_light + @color/attendance_lateness_light @color/colorError @color/colorDivider @color/colorSwipeRefresh From ed5166333ae3a574bc974737686afb0db994806a Mon Sep 17 00:00:00 2001 From: PoProstuSever <88079246+PoProstuSever@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:48:31 +0100 Subject: [PATCH 382/545] Add the ability to dynamically expand the application window (#2413) --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 174c9a1fc..f43dfdd2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="false" android:theme="@style/WulkanowyTheme" + android:resizeableActivity="true" tools:ignore="DataExtractionRules,UnusedAttribute"> Date: Thu, 8 Feb 2024 07:49:17 +0100 Subject: [PATCH 383/545] Fix displaying lessons for tomorrow if there is no more lessons for today (#2416) --- .../IsStudentHasLessonsOnWeekendUseCase.kt | 33 +++++++++++++++++++ .../timetable/IsWeekendHasLessonsUseCase.kt | 17 ++++++++++ .../modules/dashboard/DashboardPresenter.kt | 12 +++++-- .../modules/timetable/TimetablePresenter.kt | 32 ++++-------------- 4 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt create mode 100644 app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt new file mode 100644 index 000000000..efe928e2b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.domain.timetable + +import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.toFirstResult +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import java.time.LocalDate +import javax.inject.Inject + +class IsStudentHasLessonsOnWeekendUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, + private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase, +) { + + suspend operator fun invoke( + student: Student, + semester: Semester, + currentDate: LocalDate = LocalDate.now(), + ): Boolean { + val lessons = timetableRepository.getTimetable( + student = student, + semester = semester, + start = currentDate.monday, + end = currentDate.sunday, + forceRefresh = false, + timetableType = TimetableRepository.TimetableType.NORMAL + ).toFirstResult().dataOrNull?.lessons.orEmpty() + return isWeekendHasLessonsUseCase(lessons) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt new file mode 100644 index 000000000..908c9df78 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsWeekendHasLessonsUseCase.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.domain.timetable + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.DayOfWeek +import javax.inject.Inject + +class IsWeekendHasLessonsUseCase @Inject constructor() { + + operator fun invoke( + lessons: List, + ): Boolean = lessons.any { + it.date.dayOfWeek in listOf( + DayOfWeek.SATURDAY, + DayOfWeek.SUNDAY, + ) + } +} 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 ad604499b..1e6f1c198 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 @@ -24,11 +24,13 @@ 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.domain.adminmessage.GetAppropriateAdminMessageUseCase +import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.sunday import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -56,6 +58,7 @@ class DashboardPresenter @Inject constructor( private val messageRepository: MessageRepository, private val attendanceSummaryRepository: AttendanceSummaryRepository, private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, private val homeworkRepository: HomeworkRepository, private val examRepository: ExamRepository, private val conferenceRepository: ConferenceRepository, @@ -435,14 +438,17 @@ class DashboardPresenter @Inject constructor( private fun loadLessons(student: Student, forceRefresh: Boolean) { flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) - val date = LocalDate.now().nextOrSameSchoolDay + val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) { + true -> LocalDate.now() + else -> LocalDate.now().nextOrSameSchoolDay + } timetableRepository.getTimetable( student = student, semester = semester, start = date, - end = date, - forceRefresh = forceRefresh + end = date.sunday, + forceRefresh = forceRefresh, ) } .onEach { 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 6b442d1c2..7e8c876ef 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 @@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.timetable import android.os.Handler import android.os.Looper -import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable @@ -20,8 +19,8 @@ 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.data.toFirstResult -import io.github.wulkanowy.data.waitForResult +import io.github.wulkanowy.domain.timetable.IsStudentHasLessonsOnWeekendUseCase +import io.github.wulkanowy.domain.timetable.IsWeekendHasLessonsUseCase import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper @@ -31,16 +30,12 @@ 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.monday import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.until -import kotlinx.coroutines.flow.firstOrNull import timber.log.Timber -import java.time.DayOfWeek import java.time.Instant import java.time.LocalDate import java.time.LocalDate.now @@ -54,6 +49,8 @@ class TimetablePresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, + private val isWeekendHasLessonsUseCase: IsWeekendHasLessonsUseCase, private val semesterRepository: SemesterRepository, private val prefRepository: PreferencesRepository, private val analytics: AnalyticsHelper, @@ -165,7 +162,7 @@ class TimetablePresenter @Inject constructor( } .logResourceStatus("load timetable data") .onResourceData { - isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessons(it.lessons) + isWeekendHasLessons = isWeekendHasLessons || isWeekendHasLessonsUseCase(it.lessons) view?.run { enableSwipe(true) @@ -199,15 +196,7 @@ class TimetablePresenter @Inject constructor( private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { if (initialDate == null) { - val lessons = timetableRepository.getTimetable( - student = student, - semester = semester, - start = now().monday, - end = now().sunday, - forceRefresh = false, - timetableType = TimetableRepository.TimetableType.NORMAL - ).toFirstResult().dataOrNull?.lessons.orEmpty() - isWeekendHasLessons = isWeekendHasLessons(lessons) + isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester) initialDate = getInitialDate(semester) } @@ -216,15 +205,6 @@ class TimetablePresenter @Inject constructor( } } - private fun isWeekendHasLessons( - lessons: List, - ): Boolean = lessons.any { - it.date.dayOfWeek in listOf( - DayOfWeek.SATURDAY, - DayOfWeek.SUNDAY, - ) - } - private fun getInitialDate(semester: Semester): LocalDate { val now = now() From 22f72981cb7eea613f6b6ac234fbb7c050ccccf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 8 Feb 2024 09:16:09 +0100 Subject: [PATCH 384/545] Add descriptive grades (#2411) --- .../59.json | 2501 +++++++++++++++++ .../io/github/wulkanowy/data/DataModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 10 +- .../data/db/dao/GradeDescriptiveDao.kt | 15 + .../data/db/entities/GradeDescriptive.kt | 27 + .../wulkanowy/data/mappers/GradeMapper.kt | 16 +- .../data/repositories/GradeRepository.kt | 63 +- .../notifications/NewGradeNotification.kt | 26 +- .../sync/notifications/NotificationType.kt | 4 + .../services/sync/works/GradeWork.kt | 10 + .../NotificationDebugPresenter.kt | 9 + .../notification/mock/gradeDescriptive.kt | 48 + .../ui/modules/grade/GradeAverageProvider.kt | 93 +- .../ui/modules/grade/GradeSubject.kt | 2 + .../grade/details/GradeDetailsPresenter.kt | 25 +- .../grade/summary/GradeSummaryAdapter.kt | 52 +- .../grade/summary/GradeSummaryFragment.kt | 4 +- .../modules/grade/summary/GradeSummaryItem.kt | 9 + .../grade/summary/GradeSummaryPresenter.kt | 36 +- .../modules/grade/summary/GradeSummaryView.kt | 3 +- .../main/res/layout/item_grade_summary.xml | 46 +- app/src/main/res/values/strings.xml | 9 + .../data/repositories/GradeRepositoryTest.kt | 14 +- .../modules/grade/GradeAverageProviderTest.kt | 446 ++- 24 files changed, 3248 insertions(+), 224 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json new file mode 100644 index 000000000..a3f2e0dc6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/59.json @@ -0,0 +1,2501 @@ +{ + "formatVersion": 1, + "database": { + "version": 59, + "identityHash": "3bd95e40b587e8131a2a2c23aee538c1", + "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, '3bd95e40b587e8131a2a2c23aee538c1')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 950e817bb..7c9cf9a3c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -253,4 +253,8 @@ internal class DataModule { @Singleton @Provides fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao + + @Singleton + @Provides + fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao } 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 a2f230f4c..8e5841fe7 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 @@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao @@ -44,6 +45,7 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics @@ -154,7 +156,8 @@ import javax.inject.Singleton TimetableHeader::class, SchoolAnnouncement::class, Notification::class, - AdminMessage::class + AdminMessage::class, + GradeDescriptive::class, ], autoMigrations = [ AutoMigration(from = 44, to = 45), @@ -165,6 +168,7 @@ import javax.inject.Singleton AutoMigration(from = 55, to = 56), AutoMigration(from = 56, to = 57, spec = Migration57::class), AutoMigration(from = 57, to = 58, spec = Migration58::class), + AutoMigration(from = 58, to = 59), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -173,7 +177,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 58 + const val VERSION_SCHEMA = 59 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -298,4 +302,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val notificationDao: NotificationDao abstract val adminMessagesDao: AdminMessageDao + + abstract val gradeDescriptiveDao: GradeDescriptiveDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt new file mode 100644 index 000000000..6282c0804 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDescriptiveDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Singleton +@Dao +interface GradeDescriptiveDao : BaseDao { + + @Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt new file mode 100644 index 000000000..9aec9599a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeDescriptive.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity(tableName = "GradesDescriptive") +data class GradeDescriptive( + + @ColumnInfo(name = "semester_id") + val semesterId: Int, + + @ColumnInfo(name = "student_id") + val studentId: Int, + + val subject: String, + + val description: String, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt index 178de682a..66e922171 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt @@ -1,10 +1,12 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade +import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive +import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary fun List.mapToEntities(semester: Semester) = map { Grade( @@ -40,3 +42,15 @@ fun List.mapToEntities(semester: Semester) = map { average = it.average ) } + +@JvmName("mapGradeDescriptiveToEntities") +fun List.mapToEntities(semester: Semester) = map { + GradeDescriptive( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + description = it.description + ) +} + + diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index b2bb7b906..1e2ea9354 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -1,15 +1,22 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.toLocalDate +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map @@ -22,14 +29,13 @@ import javax.inject.Singleton class GradeRepository @Inject constructor( private val gradeDb: GradeDao, private val gradeSummaryDb: GradeSummaryDao, + private val gradeDescriptiveDb: GradeDescriptiveDao, private val sdk: Sdk, private val refreshHelper: AutoRefreshHelper, ) { private val saveFetchResultMutex = Mutex() - private val cacheKey = "grade" - fun getGrades( student: Student, semester: Semester, @@ -41,30 +47,52 @@ class GradeRepository @Inject constructor( //When details is empty and summary is not, app will not use summary cache - edge case it.first.isEmpty() }, - shouldFetch = { (details, summaries) -> - val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) - details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired + shouldFetch = { (details, summaries, descriptive) -> + val isExpired = + refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester)) + details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired }, query = { val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId) val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) - detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries } + val descriptiveFlow = + gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId) + + combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive -> + Triple(details, summaries, descriptive) + } }, fetch = { - val (details, summary) = sdk.init(student) + val (details, summary, descriptive) = sdk.init(student) .switchSemester(semester) .getGrades(semester.semesterId) - details.mapToEntities(semester) to summary.mapToEntities(semester) + Triple( + details.mapToEntities(semester), + summary.mapToEntities(semester), + descriptive.mapToEntities(semester) + ) }, - saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) -> + saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) -> refreshGradeDetails(student, oldDetails, newDetails, notify) refreshGradeSummaries(oldSummary, newSummary, notify) + refreshGradeDescriptions(oldDescriptive, newDescriptive, notify) - refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) + refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester)) } ) + private suspend fun refreshGradeDescriptions( + old: List, + new: List, + notify: Boolean + ) { + gradeDescriptiveDb.deleteAll(old uniqueSubtract new) + gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }) + } + private suspend fun refreshGradeDetails( student: Student, oldGrades: List, @@ -132,6 +160,10 @@ class GradeRepository @Inject constructor( return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) } + fun getGradesDescriptiveFromDatabase(semester: Semester): Flow> { + return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId) + } + suspend fun updateGrade(grade: Grade) { return gradeDb.updateAll(listOf(grade)) } @@ -143,4 +175,13 @@ class GradeRepository @Inject constructor( suspend fun updateGradesSummary(gradesSummary: List) { return gradeSummaryDb.updateAll(gradesSummary) } + + suspend fun updateGradesDescriptive(gradesDescriptive: List) { + return gradeDescriptiveDb.updateAll(gradesDescriptive) + } + + private companion object { + + private const val GRADE_CACHE_KEY = "grade" + } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt index 9b49ed178..7f90bbddc 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt @@ -4,12 +4,12 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.ui.modules.Destination -import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.getPlural import javax.inject.Inject @@ -88,4 +88,28 @@ class NewGradeNotification @Inject constructor( appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } + + suspend fun notifyDescriptive(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_descriptive, 1), + content = "${it.subject}: ${it.description}", + destination = Destination.Grade, + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_descriptive, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_descriptive, + items.size, + items.size + ), + destination = Destination.Grade, + type = NotificationType.NEW_GRADE_DESCRIPTIVE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt index 023ae2e4c..4e7f27351 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt @@ -37,6 +37,10 @@ enum class NotificationType( channel = NewGradesChannel.CHANNEL_ID, icon = R.drawable.ic_stat_grade, ), + NEW_GRADE_DESCRIPTIVE( + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), NEW_HOMEWORK( channel = NewHomeworkChannel.CHANNEL_ID, icon = R.drawable.ic_more_homework, diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index ba21b8600..b62ad94b9 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -45,5 +45,15 @@ class GradeWork @Inject constructor( grade.isFinalGradeNotified = true }) } + + gradeRepository.getGradesDescriptiveFromDatabase(semester).first() + .filter { !it.isNotified } + .let { + if (it.isNotEmpty()) newGradeNotification.notifyDescriptive(it, student) + + gradeRepository.updateGradesDescriptive(it.onEach { grade -> + grade.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt index d0dfcd696..cdd186b94 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDescriptiveItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeSummaryItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugHomeworkItems @@ -55,6 +56,14 @@ class NotificationDebugPresenter @Inject constructor( NotificationDebugItem(R.string.grade_summary_final_grade) { n -> withStudent { newGradeNotification.notifyFinal(debugGradeSummaryItems.take(n), it) } }, + NotificationDebugItem(R.string.grade_summary_descriptive) { n -> + withStudent { + newGradeNotification.notifyDescriptive( + debugGradeDescriptiveItems.take(n), + it + ) + } + }, NotificationDebugItem(R.string.homework_title) { n -> withStudent { newHomeworkNotification.notify(debugHomeworkItems.take(n), it) } }, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt new file mode 100644 index 000000000..d5a0c089b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeDescriptive.kt @@ -0,0 +1,48 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.GradeDescriptive + +val debugGradeDescriptiveItems = listOf( + generateGradeDescriptive( + "Matematyka", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive("Fizyka", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."), + generateGradeDescriptive( + "Geografia", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Sieci komputerowe", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Systemy operacyjne", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Język polski", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Język angielski", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive("Religia", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."), + generateGradeDescriptive( + "Język niemiecki", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), + generateGradeDescriptive( + "Wychowanie fizyczne", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ), +) + +private fun generateGradeDescriptive(subject: String, description: String) = + GradeDescriptive( + semesterId = 0, + studentId = 0, + subject = subject, + description = description + ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index ec4bd8e8c..e8a5fa254 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,15 +1,23 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.errorOrNull +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.mapData +import io.github.wulkanowy.data.mapResourceData import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS +import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,6 +70,7 @@ class GradeAverageProvider @Inject constructor( forceRefresh = forceRefresh, params = params, ) + BOTH_SEMESTERS -> calculateCombinedAverage( student = student, semesters = semesters, @@ -69,6 +78,7 @@ class GradeAverageProvider @Inject constructor( forceRefresh = forceRefresh, config = params, ) + ALL_YEAR -> calculateCombinedAverage( student = student, semesters = semesters, @@ -189,36 +199,73 @@ class GradeAverageProvider @Inject constructor( ): Flow>> { return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) .mapResourceData { res -> - val (details, summaries) = res + val (details, summaries, descriptives) = res val isAnyAverage = summaries.any { it.average != .0 } val allGrades = details.groupBy { it.subject } + val descriptiveGradesBySubject = descriptives.associateBy { it.subject } - val items = summaries.emulateEmptySummaries( - student = student, - semester = semester, - grades = allGrades.toList(), - calcAverage = isAnyAverage, - params = params, - ).map { summary -> - val grades = allGrades[summary.subject].orEmpty() - GradeSubject( - subject = summary.subject, - average = if (!isAnyAverage || params.forceAverageCalc) { - grades.updateModifiers(student, params) - .calcAverage(params.isOptionalArithmeticAverage) - } else summary.average, - points = summary.pointsSum, - summary = summary, - grades = grades, - isVulcanAverage = isAnyAverage + val items = summaries + .createEmptySummariesByGradesIfNeeded( + student = student, + semester = semester, + grades = allGrades.toList(), + calcAverage = isAnyAverage, + params = params, ) - } + .createEmptySummariesByDescriptiveGradesIfNeeded( + student = student, + semester = semester, + descriptives = descriptives, + ) + .map { summary -> + val grades = allGrades[summary.subject].orEmpty() + val descriptiveGrade = descriptiveGradesBySubject[summary.subject] + + GradeSubject( + subject = summary.subject, + average = if (!isAnyAverage || params.forceAverageCalc) { + grades.updateModifiers(student, params) + .calcAverage(params.isOptionalArithmeticAverage) + } else summary.average, + points = summary.pointsSum, + summary = summary, + grades = grades, + descriptive = descriptiveGrade, + isVulcanAverage = isAnyAverage + ) + } items } } - private fun List.emulateEmptySummaries( + private fun List.createEmptySummariesByDescriptiveGradesIfNeeded( + student: Student, + semester: Semester, + descriptives: List + ): List { + val summarySubjects = this.map { it.subject } + val gradeSummaryToAdd = descriptives.mapNotNull { gradeDescriptive -> + if (gradeDescriptive.subject in summarySubjects) return@mapNotNull null + + GradeSummary( + studentId = student.studentId, + semesterId = semester.semesterId, + position = 0, + subject = gradeDescriptive.subject, + predictedGrade = "", + finalGrade = "", + proposedPoints = "", + finalPoints = "", + pointsSum = "", + average = .0 + ) + } + + return this + gradeSummaryToAdd + } + + private fun List.createEmptySummariesByGradesIfNeeded( student: Student, semester: Semester, grades: List>>, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt index 57be55ee3..a465551f9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeSubject.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeDescriptive import io.github.wulkanowy.data.db.entities.GradeSummary data class GradeSubject( @@ -8,6 +9,7 @@ data class GradeSubject( val average: Double, val points: String, val summary: GradeSummary, + val descriptive: GradeDescriptive?, val grades: List, val isVulcanAverage: Boolean ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 4261c507d..d9621f51e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,13 +1,22 @@ package io.github.wulkanowy.ui.modules.grade.details -import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeExpandMode -import io.github.wulkanowy.data.enums.GradeSortingMode.* +import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC +import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE +import io.github.wulkanowy.data.enums.GradeSortingMode.DATE +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.GradeRepository 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.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider @@ -207,20 +216,20 @@ class GradeDetailsPresenter @Inject constructor( AVERAGE -> gradeSubjects.sortedByDescending { it.average } } } - .map { (subject, average, points, _, grades) -> - val subItems = grades + .map { gradeSubject -> + val subItems = gradeSubject.grades .sortedByDescending { it.date } .map { GradeDetailsItem(it, ViewType.ITEM) } val gradeDetailsItems = listOf( GradeDetailsItem( GradeDetailsHeader( - subject = subject, - average = average, - pointsSum = points, + subject = gradeSubject.subject, + average = gradeSubject.average, + pointsSum = gradeSubject.points, grades = subItems ).apply { - newGrades = grades.filter { grade -> !grade.isRead }.size + newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size }, ViewType.HEADER ) ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt index 8dcade56e..95cf97bed 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -2,16 +2,16 @@ package io.github.wulkanowy.ui.modules.grade.summary import android.annotation.SuppressLint import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.ItemGradeSummaryBinding import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid import io.github.wulkanowy.utils.calcFinalAverage +import io.github.wulkanowy.utils.ifNullOrBlank import java.util.Locale import javax.inject.Inject @@ -24,7 +24,7 @@ class GradeSummaryAdapter @Inject constructor( ITEM(2) } - var items = emptyList() + var items = emptyList() var onCalculatedHelpClickListener: () -> Unit = {} @@ -44,9 +44,11 @@ class GradeSummaryAdapter @Inject constructor( ViewType.HEADER.id -> HeaderViewHolder( ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false) ) + ViewType.ITEM.id -> ItemViewHolder( ItemGradeSummaryBinding.inflate(inflater, parent, false) ) + else -> throw IllegalStateException() } } @@ -60,19 +62,23 @@ class GradeSummaryAdapter @Inject constructor( private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) { if (items.isEmpty()) return + val gradeSummaries = items + .filter { it.gradeDescriptive == null } + .map { it.gradeSummary } val context = binding.root.context - val finalItemsCount = items.count { isGradeValid(it.finalGrade) } - val calculatedItemsCount = items.count { value -> value.average != 0.0 } - val allItemsCount = items.count { !it.subject.equals("zachowanie", true) } - val finalAverage = items.calcFinalAverage( + val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) } + val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 } + val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) } + val finalAverage = gradeSummaries.calcFinalAverage( preferencesRepository.gradePlusModifier, preferencesRepository.gradeMinusModifier ) - val calculatedAverage = items.filter { value -> value.average != 0.0 } + val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 } .map { values -> values.average } .reversed() // fix average precision .average() + .let { if (it.isNaN()) 0.0 else it } with(binding) { gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) @@ -95,16 +101,28 @@ class GradeSummaryAdapter @Inject constructor( } @SuppressLint("SetTextI18n") - private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) { - with(binding) { - gradeSummaryItemTitle.text = item.subject - gradeSummaryItemPoints.text = item.pointsSum - gradeSummaryItemAverage.text = formatAverage(item.average, "") - gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim() - gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim() + private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummaryItem) { + val (gradeSummary, gradeDescriptive) = item - gradeSummaryItemPointsContainer.visibility = - if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE + with(binding) { + gradeSummaryItemTitle.text = gradeSummary.subject + gradeSummaryItemPoints.text = gradeSummary.pointsSum + gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "") + gradeSummaryItemPredicted.text = + "${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim() + gradeSummaryItemFinal.text = + "${gradeSummary.finalGrade} ${gradeSummary.finalPoints}".trim() + gradeSummaryItemDescriptive.text = gradeDescriptive?.description.ifNullOrBlank { + root.context.getString(R.string.all_no_data) + } + + gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null + gradeSummaryItemPredictedContainer.isVisible = gradeDescriptive == null + gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null + gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null + gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index abd0b13c4..35b2edd58 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -5,12 +5,10 @@ import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment @@ -72,7 +70,7 @@ class GradeSummaryFragment : } } - override fun updateData(data: List) { + override fun updateData(data: List) { with(gradeSummaryAdapter) { items = data notifyDataSetChanged() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt new file mode 100644 index 000000000..cf0f1d92e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.ui.modules.grade.summary + +import io.github.wulkanowy.data.db.entities.GradeDescriptive +import io.github.wulkanowy.data.db.entities.GradeSummary + +data class GradeSummaryItem( + val gradeSummary: GradeSummary, + val gradeDescriptive: GradeDescriptive? +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 32508ff6f..d762df02b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,9 +1,16 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.* -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.enums.GradeSortingMode -import io.github.wulkanowy.data.enums.GradeSortingMode.* +import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC +import io.github.wulkanowy.data.enums.GradeSortingMode.AVERAGE +import io.github.wulkanowy.data.enums.GradeSortingMode.DATE +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mapResourceData +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.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -128,7 +135,7 @@ class GradeSummaryPresenter @Inject constructor( view?.showFinalAverageHelpDialog() } - private fun createGradeSummaryItems(items: List): List { + private fun createGradeSummaryItems(items: List): List { return items .filter { !checkEmpty(it) } .let { gradeSubjects -> @@ -136,21 +143,32 @@ class GradeSummaryPresenter @Inject constructor( DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage -> gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date } + ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage -> gradeDetailsWithAverage.subject.lowercase() } + AVERAGE -> gradeSubjects.sortedByDescending { it.average } } } - .map { it.summary.copy(average = it.average) } + .map { + val gradeSummary = it.summary.copy(average = it.average) + val descriptive = it.descriptive + GradeSummaryItem( + gradeSummary = gradeSummary, + gradeDescriptive = descriptive, + ) + } + } private fun checkEmpty(gradeSummary: GradeSubject): Boolean { return gradeSummary.run { summary.finalGrade.isBlank() - && summary.predictedGrade.isBlank() - && average == .0 - && points.isBlank() + && summary.predictedGrade.isBlank() + && average == .0 + && points.isBlank() + && descriptive == null } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt index 156731c31..36bd61421 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryView.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.ui.base.BaseView interface GradeSummaryView : BaseView { @@ -13,7 +12,7 @@ interface GradeSummaryView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List) fun resetView() diff --git a/app/src/main/res/layout/item_grade_summary.xml b/app/src/main/res/layout/item_grade_summary.xml index 30aa6e77b..2c8c4ea37 100644 --- a/app/src/main/res/layout/item_grade_summary.xml +++ b/app/src/main/res/layout/item_grade_summary.xml @@ -64,10 +64,12 @@ + android:layout_height="1dp" + android:id="@+id/gradeSummaryItemPointsDivider" + android:background="@drawable/ic_all_divider" /> + android:id="@+id/gradeSummaryItemPredictedDivider" + android:layout_height="1dp" + android:background="@drawable/ic_all_divider" /> + android:layout_height="1dp" + android:id="@+id/gradeSummaryItemFinalDivider" + android:background="@drawable/ic_all_divider" /> + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 60d85606d..f1fa3ce73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,6 +130,7 @@ Total points Final grade Predicted grade + Descriptive 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 @@ -165,6 +166,10 @@ New final grade New final grades + + New descriptive grade + New descriptive grades + You received %1$d grade You received %1$d grades @@ -177,6 +182,10 @@ You received %1$d final grade You received %1$d final grades + + You received %1$d descriptive grade + You received %1$d descriptive grades + 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 5a1877cc0..515b0d66d 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 @@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities @@ -42,6 +43,9 @@ class GradeRepositoryTest { @MockK private lateinit var gradeSummaryDb: GradeSummaryDao + @MockK + private lateinit var gradeDescriptiveDb: GradeDescriptiveDao + @MockK(relaxUnitFun = true) private lateinit var refreshHelper: AutoRefreshHelper @@ -56,7 +60,8 @@ class GradeRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - gradeRepository = GradeRepository(gradeDb, gradeSummaryDb, sdk, refreshHelper) + gradeRepository = + GradeRepository(gradeDb, gradeSummaryDb, gradeDescriptiveDb, sdk, refreshHelper) coEvery { gradeDb.deleteAll(any()) } just Runs coEvery { gradeDb.insertAll(any()) } returns listOf() @@ -68,6 +73,13 @@ class GradeRepositoryTest { ) coEvery { gradeSummaryDb.deleteAll(any()) } just Runs coEvery { gradeSummaryDb.insertAll(any()) } returns listOf() + + coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf( + flowOf(listOf()), + ) + + coEvery { gradeDescriptiveDb.deleteAll(any()) } just Runs + coEvery { gradeDescriptiveDb.insertAll(any()) } returns listOf() } @Test 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 31ea3322b..6a717f6f6 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 @@ -1,12 +1,16 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.Status @@ -158,7 +162,9 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { noWeightGrades to noWeightGradesSummary } + } returns resourceFlow { + Triple(noWeightGrades, noWeightGradesSummary, emptyList()) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -186,7 +192,9 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary } + } returns resourceFlow { + Triple(noWeightGrades, noWeightGradesArithmeticSummary, emptyList()) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -211,8 +219,24 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { emit(Resource.Loading()) - emit(Resource.Intermediate(secondGradeWithModifier to secondSummariesWithModifier)) - emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) + emit( + Resource.Intermediate( + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + ) + ) + emit( + Resource.Success( + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + ) + ) } val items = runBlocking { @@ -253,11 +277,27 @@ class GradeAverageProviderTest { coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow { emit(Resource.Loading()) delay(1000) - emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) + emit( + Resource.Success( + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + ) + ) } coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow { emit(Resource.Loading()) - emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) + emit( + Resource.Success( + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + ) + ) } val items = runBlocking { @@ -296,7 +336,13 @@ class GradeAverageProviderTest { semesters[1], false ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } coEvery { gradeRepository.getGrades( student, @@ -304,8 +350,10 @@ class GradeAverageProviderTest { false ) } returns resourceFlow { - listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf( - getSummary(semesters[2].semesterId, "Język polski", 2.5) + Triple( + listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)), + listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5)), + emptyList() ) } @@ -332,7 +380,13 @@ class GradeAverageProviderTest { semesters[1], false ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } coEvery { gradeRepository.getGrades( student, @@ -340,12 +394,14 @@ class GradeAverageProviderTest { false ) } returns resourceFlow { - emptyList() to listOf( - getSummary( - 24, - "Język polski", - .0 - ) + Triple( + emptyList(), listOf( + getSummary( + 24, + "Język polski", + .0 + ) + ), emptyList() ) } @@ -372,14 +428,22 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { emptyList() to emptyList() } + } returns resourceFlow { + Triple( + emptyList(), + emptyList(), + emptyList() + ) + } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { emptyList() to emptyList() } + } returns resourceFlow { + Triple(emptyList(), emptyList(), emptyList()) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -404,7 +468,13 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -438,7 +508,13 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -472,7 +548,13 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -506,7 +588,13 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + } returns resourceFlow { + Triple( + secondGradeWithModifier, + secondSummariesWithModifier, + emptyList() + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -534,7 +622,7 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGrades to secondSummaries } + } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -564,7 +652,7 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns resourceFlow { secondGrades to secondSummaries } + } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -594,7 +682,7 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to firstSummaries } + } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -625,8 +713,8 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { emit(Resource.Loading()) - emit(Resource.Intermediate(firstGrades to firstSummaries)) - emit(Resource.Success(firstGrades to firstSummaries)) + emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList()))) + emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList()))) } val items = runBlocking { @@ -675,9 +763,11 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) + Triple( + firstGrades, listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ), emptyList() ) } coEvery { @@ -687,9 +777,13 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) + Triple( + secondGrades, + listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ), + emptyList() ) } @@ -723,17 +817,21 @@ class GradeAverageProviderTest { emit(Resource.Loading()) emit( Resource.Intermediate( - firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) + Triple( + firstGrades, listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ), emptyList() ) ) ) emit( Resource.Success( - firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) + Triple( + firstGrades, listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ), emptyList() ) ) ) @@ -742,17 +840,21 @@ class GradeAverageProviderTest { emit(Resource.Loading()) emit( Resource.Intermediate( - secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ), emptyList() ) ) ) emit( Resource.Success( - secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ), emptyList() ) ) ) @@ -803,7 +905,7 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to firstSummaries } + } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) } coEvery { gradeRepository.getGrades( student, @@ -811,9 +913,11 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ), emptyList() ) } @@ -850,9 +954,11 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - firstGrades to listOf( - getSummary(22, "Matematyka", .0), - getSummary(22, "Fizyka", .0) + Triple( + firstGrades, listOf( + getSummary(22, "Matematyka", .0), + getSummary(22, "Fizyka", .0) + ), emptyList() ) } coEvery { @@ -862,9 +968,11 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - secondGrades to listOf( - getSummary(22, "Matematyka", .0), - getSummary(22, "Fizyka", .0) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", .0), + getSummary(22, "Fizyka", .0) + ), emptyList() ) } @@ -889,24 +997,28 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { emit(Resource.Loading()) - emit(Resource.Intermediate(firstGrades to firstSummaries)) - emit(Resource.Success(firstGrades to firstSummaries)) + emit(Resource.Intermediate(Triple(firstGrades, firstSummaries, emptyList()))) + emit(Resource.Success(Triple(firstGrades, firstSummaries, emptyList()))) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { emit(Resource.Loading()) emit( Resource.Intermediate( - secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ), emptyList() ) ) ) emit( Resource.Success( - secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) + Triple( + secondGrades, listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ), emptyList() ) ) ) @@ -958,14 +1070,14 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to emptyList() } + } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { secondGrades to emptyList() } + } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -1000,14 +1112,14 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to emptyList() } + } returns resourceFlow { Triple(firstGrades, emptyList(), emptyList()) } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { secondGrades to emptyList() } + } returns resourceFlow { Triple(secondGrades, emptyList(), emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -1043,8 +1155,10 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - firstGrades to listOf( - getSummary(22, "Matematyka", 4.0) + Triple( + firstGrades, listOf( + getSummary(22, "Matematyka", 4.0) + ), emptyList() ) } coEvery { @@ -1054,8 +1168,10 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - secondGrades to listOf( - getSummary(23, "Matematyka", 3.0) + Triple( + secondGrades, listOf( + getSummary(23, "Matematyka", 3.0) + ), emptyList() ) } @@ -1092,14 +1208,20 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to firstSummaries } + } returns resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { secondGrades to secondSummaries.dropLast(1) } + } returns resourceFlow { + Triple( + secondGrades, + secondSummaries.dropLast(1), + emptyList() + ) + } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -1134,14 +1256,20 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + } returns resourceFlow { + Triple( + firstGrades, + firstSummaries.dropLast(1), + emptyList() + ) + } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { secondGrades to secondSummaries } + } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -1176,14 +1304,20 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + } returns resourceFlow { + Triple( + firstGrades, + firstSummaries.dropLast(1), + emptyList() + ) + } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns resourceFlow { secondGrades to secondSummaries } + } returns resourceFlow { Triple(secondGrades, secondSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -1219,16 +1353,20 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ), + listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)), + emptyList() + ) } coEvery { gradeRepository.getGrades( @@ -1237,11 +1375,15 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ), + listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)), + emptyList() + ) } val items = runBlocking { @@ -1266,23 +1408,31 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.ALL_YEAR) coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { - listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ), + listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)), + emptyList() + ) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { - listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.3, weight = 2.0) + ), + listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)), + emptyList() + ) } val items = runBlocking { @@ -1313,23 +1463,31 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { - listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ), + listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)), + emptyList() + ) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { - listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ), + listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)), + emptyList() + ) } val items = runBlocking { @@ -1366,16 +1524,20 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - listOf( - getGrade(22, "Fizyka", 5.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 5.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 4.0), - getGrade(22, "Fizyka", 6.0, weight = 2.0) - ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(22, "Fizyka", 5.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 5.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 4.0), + getGrade(22, "Fizyka", 6.0, weight = 2.0) + ), + listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)), + emptyList() + ) } coEvery { gradeRepository.getGrades( @@ -1384,11 +1546,15 @@ class GradeAverageProviderTest { true ) } returns resourceFlow { - listOf( - getGrade(23, "Fizyka", 5.0, weight = 1.0), - getGrade(23, "Fizyka", 5.0, weight = 2.0), - getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) - ) to listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)) + Triple( + listOf( + getGrade(23, "Fizyka", 5.0, weight = 1.0), + getGrade(23, "Fizyka", 5.0, weight = 2.0), + getGrade(23, "Fizyka", 4.0, modifier = 0.33, weight = 2.0) + ), + listOf(getSummary(semesterId = 23, subject = "Fizyka", average = .0)), + emptyList() + ) } val items = runBlocking { @@ -1413,9 +1579,9 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(false) coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns - resourceFlow { firstGrades to firstSummaries } + resourceFlow { Triple(firstGrades, firstSummaries, emptyList()) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns - resourceFlow { listOf() to firstSummaries } + resourceFlow { Triple(listOf(), firstSummaries, emptyList()) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( From 3f199cb610016c72baaa89ae369205125c864411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 9 Feb 2024 13:40:01 +0100 Subject: [PATCH 385/545] Replace fakelog.cf to wulkanowy.net.pl (#2419) --- .../ui/modules/login/advanced/LoginAdvancedPresenter.kt | 4 ++-- .../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 2 +- .../ui/modules/login/recover/LoginRecoverPresenter.kt | 2 +- app/src/main/res/values/api_hosts.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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 a17ad0035..fc26f3765 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 @@ -71,7 +71,7 @@ class LoginAdvancedPresenter @Inject constructor( fun updateUsernameLabel() { view?.apply { - setUsernameLabel(if ("vulcan" in formHostValue || "fakelog" in formHostValue) emailLabel else nicknameLabel) + setUsernameLabel(if ("vulcan" in formHostValue || "wulkanowy" in formHostValue) emailLabel else nicknameLabel) } } @@ -79,7 +79,7 @@ class LoginAdvancedPresenter @Inject constructor( view?.apply { clearPassError() clearUsernameError() - if (formHostValue.contains("fakelog")) { + if (formHostValue.contains("wulkanowy")) { setDefaultCredentials( "jan@fakelog.cf", "jan123", "powiatwulkanowy", "FK100000", "999999" ) 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 c9ae4f27f..26b15bff0 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 @@ -90,7 +90,7 @@ class LoginFormPresenter @Inject constructor( clearPassError() clearUsernameError() clearHostError() - if (formHostValue.contains("fakelog")) { + if (formHostValue.contains("wulkanowy")) { setCredentials("jan@fakelog.cf", "jan123") } else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") { setCredentials("", "") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index a424df40d..879055a9a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -38,7 +38,7 @@ class LoginRecoverPresenter @Inject constructor( fun onHostSelected() { view?.run { - if ("fakelog" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf") + if ("wulkanowy" in recoverHostValue) setDefaultCredentials("jan@fakelog.cf") clearUsernameError() updateFields() } diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 522b6e116..94ef8abdc 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -44,7 +44,7 @@ https://vulcan.net.pl/?login https://vulcan.net.pl/?login https://vulcan.net.pl/?email&customSuffix - https://fakelog.cf/?email + https://wulkanowy.net.pl/?email Default From 8183d7d5a0f9f62e7d63a8a42f15817525e11d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 9 Feb 2024 16:35:18 +0100 Subject: [PATCH 386/545] Change default symbol for standard register variant (#2421) --- app/build.gradle | 2 +- .../java/io/github/wulkanowy/ui/modules/login/LoginData.kt | 3 ++- .../ui/modules/login/advanced/LoginAdvancedPresenter.kt | 2 +- .../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 4 ++-- .../ui/modules/login/recover/LoginRecoverPresenter.kt | 4 ++-- .../login/studentselect/LoginStudentSelectFragment.kt | 2 -- .../login/studentselect/LoginStudentSelectPresenter.kt | 6 +++--- .../ui/modules/login/support/LoginSupportDialog.kt | 2 +- .../ui/modules/login/symbol/LoginSymbolPresenter.kt | 6 +++--- app/src/main/res/values/api_hosts.xml | 4 ++-- app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt | 1 + .../login/studentselect/LoginStudentSelectPresenterTest.kt | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b60dc3b4b..65a42ce1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.7' + implementation 'io.github.wulkanowy:sdk:2.3.8-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 2c11bb6d5..b066cceb9 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 @@ -7,5 +7,6 @@ data class LoginData( val password: String, val baseUrl: String, val domainSuffix: String, - val symbol: String?, + val defaultSymbol: String, + val userEnteredSymbol: String? = null, ) : Serializable 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 fc26f3765..009f26e17 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 @@ -155,7 +155,7 @@ class LoginAdvancedPresenter @Inject constructor( password = view?.formPassValue.orEmpty().trim(), baseUrl = view?.formHostValue.orEmpty().trim(), domainSuffix = view?.formDomainSuffix.orEmpty().trim(), - symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), + defaultSymbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(), ) when (it.data.symbols.size) { 0 -> view?.navigateToSymbol(loginData) 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 26b15bff0..af89f147c 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 @@ -148,7 +148,7 @@ class LoginFormPresenter @Inject constructor( password = password, baseUrl = host, domainSuffix = domainSuffix, - symbol = symbol + defaultSymbol = symbol ) } @@ -167,7 +167,7 @@ class LoginFormPresenter @Inject constructor( password = loginData.password, scrapperBaseUrl = loginData.baseUrl, domainSuffix = loginData.domainSuffix, - symbol = loginData.symbol.orEmpty(), + symbol = loginData.defaultSymbol, ) } .logResourceStatus("login") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 879055a9a..18902e014 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -60,7 +60,7 @@ class LoginRecoverPresenter @Inject constructor( resourceFlow { recoverRepository.getReCaptchaSiteKey( host, - symbol.ifBlank { "Default" }) + symbol.ifBlank { "default" }) }.onEach { when (it) { is Resource.Loading -> view?.run { @@ -103,7 +103,7 @@ class LoginRecoverPresenter @Inject constructor( fun onReCaptchaVerified(reCaptchaResponse: String) { val username = view?.recoverNameValue.orEmpty() val host = view?.recoverHostValue.orEmpty() - val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } + val symbol = view?.formHostSymbol.ifNullOrBlank { "default" } resourceFlow { recoverRepository.sendRecoverRequest( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 06efd8d98..0fe36aa99 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -10,13 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.ui.base.BaseFragment -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.serializable import javax.inject.Inject 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 6cbdfbb85..344414180 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 @@ -111,8 +111,8 @@ class LoginStudentSelectPresenter @Inject constructor( val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } - if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) { - add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol })) + if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) { + add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol })) } addAll(createNotEmptySymbolItems(notEmptySymbols, students)) @@ -317,7 +317,7 @@ class LoginStudentSelectPresenter @Inject constructor( loginData = loginData, registerUser = registerUser, lastErrorMessage = lastError?.message, - enteredSymbol = loginData.symbol, + enteredSymbol = loginData.userEnteredSymbol, ) ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt index fcf7f51c9..4be2dbaad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/support/LoginSupportDialog.kt @@ -105,7 +105,7 @@ class LoginSupportDialog : BaseDialogFragment() { "${appInfo.systemManufacturer} ${appInfo.systemModel}", appInfo.systemVersion.toString(), "${appInfo.versionName}-${appInfo.buildFlavor}", - supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol, + supportInfo.loginData.let { "${it.baseUrl}/${it.defaultSymbol}/${it.userEnteredSymbol}" }, preferencesRepository.installationId, getLastErrorFromStudentSelectScreen(), dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() } 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 02bfde5d7..cc88b09e9 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 @@ -60,7 +60,7 @@ class LoginSymbolPresenter @Inject constructor( } loginData = loginData.copy( - symbol = view?.symbolValue?.getNormalizedSymbol(), + userEnteredSymbol = view?.symbolValue?.getNormalizedSymbol(), ) resourceFlow { studentRepository.getUserSubjectsFromScrapper( @@ -68,7 +68,7 @@ class LoginSymbolPresenter @Inject constructor( password = loginData.password, scrapperBaseUrl = loginData.baseUrl, domainSuffix = loginData.domainSuffix, - symbol = loginData.symbol.orEmpty(), + symbol = loginData.userEnteredSymbol.orEmpty(), ) }.onEach { user -> registerUser = user.dataOrNull @@ -93,7 +93,7 @@ class LoginSymbolPresenter @Inject constructor( else -> { val enteredSymbolDetails = user.data.symbols .firstOrNull() - ?.takeIf { it.symbol == loginData.symbol } + ?.takeIf { it.symbol == loginData.userEnteredSymbol } if (enteredSymbolDetails?.error is InvalidSymbolException) { view?.run { diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 94ef8abdc..6439b462f 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -47,7 +47,7 @@ https://wulkanowy.net.pl/?email - Default + warszawa opole gdansk lublin @@ -66,7 +66,7 @@ gminaulanmajorat gminaozorkow gminalopiennikgorny - Default + warszawa powiatwulkanowy diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index eac1389f4..9f5d731b6 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -42,6 +42,7 @@ fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalD diaryName = "$semesterId", schoolYear = 1970, classId = 0, + className = "Ti", semesterNumber = semesterName, unitId = 1, start = start, 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 fad6436d8..34965f00d 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 @@ -58,7 +58,7 @@ class LoginStudentSelectPresenterTest { login = "", password = "", baseUrl = "", - symbol = null, + defaultSymbol = "warszawa", domainSuffix = "", ) From cd853e4d5720881623a18b8e1dbfbfb234640ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 9 Feb 2024 19:34:04 +0100 Subject: [PATCH 387/545] New Crowdin updates (#2417) --- app/src/main/res/values-cs/strings.xml | 13 +++++++++++++ app/src/main/res/values-de/strings.xml | 9 +++++++++ app/src/main/res/values-pl/strings.xml | 13 +++++++++++++ app/src/main/res/values-ru/strings.xml | 13 +++++++++++++ app/src/main/res/values-sk/strings.xml | 13 +++++++++++++ app/src/main/res/values-uk/strings.xml | 17 +++++++++++++++-- 6 files changed, 76 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b4f1f878a..85a67f9b3 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -117,6 +117,7 @@ Součet bodů Konečná známka Předpokládaná známka + Popisná známka Vypočítaný průměr Jak funguje vypočítaný průměr? Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů @@ -160,6 +161,12 @@ Nové konečné známky Nové konečné známky + + Nová popisná známka + Nové popisné známky + Nové popisné známky + Nové popisné známky + Máte %1$d novou známku Máte %1$d nové známky @@ -178,6 +185,12 @@ Máte %1$d nových konečných známek Máte %1$d nových konečných známek + + Máte %1$d novou popisnou známku + Máte %1$d nové popisné známky + Máte %1$d nových popisných známek + Máte %1$d nových popisných známek + Lekce Učebna diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7e0ce8689..0fbba392c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -117,6 +117,7 @@ Gesamtpunkte Finaler Note Vorhergesagte Note + Descriptive grade Berechnender Durchschnitt Wie funktioniert der berechnete Durchschnitt? Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte @@ -152,6 +153,10 @@ Neue Abschlussnote Neue Abschlussnoten + + New descriptive grade + New descriptive grades + Du hast %1$d Note bekommen Du hast %1$d Noten bekommen @@ -164,6 +169,10 @@ Sie haben %1$d Abschlussnote bekommen Sie haben %1$d Abschlussnoten bekommen + + You received %1$d descriptive grade + You received %1$d descriptive grades + Lektion Klassenzimmer diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1b4fbe664..df9ef8dbd 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -117,6 +117,7 @@ Suma punktów Ocena końcowa Przewidywana ocena + Ocena opisowa Obliczona średnia Jak działa obliczona średnia? Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\nŚrednia ocen tylko z wybranego semestru:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia ze średnich z obu semestrów:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia wszystkich ocen z całego roku:\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich @@ -160,6 +161,12 @@ Nowe oceny końcowe Nowe oceny końcowe + + Nowa ocena opisowa + Nowe oceny opisowe + Nowe oceny opisowe + Nowe oceny opisowe + Masz %1$d nową ocenę Masz %1$d nowe oceny @@ -178,6 +185,12 @@ Masz %1$d nowych końcowych ocen Masz %1$d nowych końcowych ocen + + Masz %1$d nową ocenę opisową + Masz %1$d nowe oceny opisowe + Masz %1$d nowych ocen opisowych + Masz %1$d nowych ocen opisowych + Lekcja Sala diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2ca669287..fffc5ce1e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -117,6 +117,7 @@ Сумма баллов Итоговая оценка Ожидаемая оценка + Descriptive grade Рассчитанная средняя оценка Как работает \"Рассчитанная средняя оценка\"? Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел @@ -160,6 +161,12 @@ Новые итоговые оценки Новые итоговые оценки + + New descriptive grade + New descriptive grades + New descriptive grades + New descriptive grades + Вы получили %1$d новую оценку Вы получили %1$d новые оценки @@ -178,6 +185,12 @@ Вы получили %1$d новых итоговых оценок Вы получили %1$d новых итоговые оценки + + You received %1$d descriptive grade + You received %1$d descriptive grades + You received %1$d descriptive grades + You received %1$d descriptive grades + Урок Аудитория diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index aaf04bc85..1e822890b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -117,6 +117,7 @@ Súčet bodov Konečná známka Predpokladaná známka + Popisná známka Vypočítaný priemer Ako funguje vypočítaný priemer? Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\nPriemer známok iba z vybraného semestra:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer priemerov z oboch semestrov:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer známok z celého roka:\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov @@ -160,6 +161,12 @@ Nové konečné známky Nové konečné známky + + Nová popisná známka + Nové popisné známky + Nové popisné známky + Nové popisné známky + Máte %1$d novú známku Máte %1$d nové známky @@ -178,6 +185,12 @@ Máte %1$d nových konečných známok Máte %1$d nových konečných známok + + Máte %1$d novú popisnú známku + Máte %1$d nové popisné známky + Máte %1$d nových popisných známok + Máte %1$d nových popisných známok + Lekcia Učebňa diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fffae003b..47034de62 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -97,8 +97,8 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову - Your account password has been changed. You need to log in to Wulkanowy again - Password changed + Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову + Пароль змінено Підтримка додатку Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час Увімкнути рекламу @@ -117,6 +117,7 @@ Всього балів Підсумкова оцінка Передбачувана оцінка + Описова оцінка Розрахована середня оцінка Як працює \"Розрахована середня оцінка\"? Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх @@ -160,6 +161,12 @@ Нові підсумкові оцінки Нові підсумкові оцінки + + Нова описова оцінка + Нових описових оцінок + Описових оцінок + Нові описові оцінки + Ви отримали %1$d нову оцінку Ви отримали %1$d нові оцінки @@ -178,6 +185,12 @@ Ви отримали %1$d нових підсумкових оцінок Ви отримали %1$d нових підсумкових оцінок + + Ви отримали %1$d описову оцінку + Ви отримали %1$d нові описові оцінки + Ви отримали %1$d нових описових оцінок + Ви отримали %1$d нових описових оцінок + Урок Аудиторія From 2d4a1bff830b172232b6ca7074f097057bc5a608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 9 Feb 2024 19:44:55 +0100 Subject: [PATCH 388/545] Version 2.4.0 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 65a42ce1d..766bee8dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 145 - versionName "2.3.5" + versionCode 146 + versionName "2.4.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,7 +164,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.15d + userFraction = 0.50d updatePriority = 1 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.8-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.4.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 04f3ba463..012bbd26d 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,8 @@ -Wersja 2.3.5 +Wersja 2.4.0 -— naprawiliśmy ładowanie frekwencji dla szkół używających eduOne -— naprawiliśmy wielokrotne wysyłanie powiadomień o zmianach w planie lekcji +— naprawiliśmy logowanie do aplikacji na odmianie standardowej +— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start +— dodaliśmy oceny opisowe +— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From cc01525f167d4d313bc8f1a48a1d8e895e6db431 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:49:46 +0000 Subject: [PATCH 389/545] Bump com.google.gms:google-services from 4.4.0 to 4.4.1 (#2423) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e6e090b80..f7f3d209e 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16" classpath 'com.android.tools.build:gradle:8.2.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.4.0' + classpath 'com.google.gms:google-services:4.4.1' classpath 'com.huawei.agconnect:agcp:1.9.1.303' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" From b5e17c4ff7b607eed9d1f9b65e82114c7b00d01a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:50:07 +0000 Subject: [PATCH 390/545] Bump com.google.firebase:firebase-bom from 32.7.1 to 32.7.2 (#2424) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 766bee8dd..5ca8c3b5b 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.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.7.1') + playImplementation platform('com.google.firebase:firebase-bom:32.7.2') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From 6f4a8d5534916eabfa32f21dbd34139bbac1ced7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 17 Feb 2024 11:41:32 +0100 Subject: [PATCH 391/545] Add missing symbol and custom domain suffix validation (#2425) --- app/build.gradle | 2 +- .../modules/login/form/LoginFormFragment.kt | 11 ++++++ .../modules/login/form/LoginFormPresenter.kt | 36 +++++++++++++------ .../ui/modules/login/form/LoginFormView.kt | 4 +++ .../login/symbol/LoginSymbolPresenter.kt | 15 +++++--- .../main/res/layout/fragment_login_form.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 55 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5ca8c3b5b..f40b74dbb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.4.0' + implementation 'io.github.wulkanowy:sdk:2.4.1-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 975cad185..1c4920696 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 @@ -94,6 +94,7 @@ class LoginFormFragment : BaseFragment(R.layout.fragme loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } + loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() } loginFormSignIn.setOnClickListener { presenter.onSignInClick() } loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } @@ -188,6 +189,12 @@ class LoginFormFragment : BaseFragment(R.layout.fragme } } + override fun setDomainSuffixInvalid() { + with(binding.loginFormDomainSuffixLayout) { + error = getString(R.string.login_invalid_domain_suffix) + } + } + override fun clearUsernameError() { binding.loginFormUsernameLayout.error = null binding.loginFormErrorBox.isVisible = false @@ -206,6 +213,10 @@ class LoginFormFragment : BaseFragment(R.layout.fragme binding.loginFormErrorBox.isVisible = false } + override fun clearDomainSuffixError() { + binding.loginFormDomainSuffixLayout.error = null + } + override fun showSoftKeyboard() { activity?.showSoftInput() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index af89f147c..69e1d027d 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 @@ -101,6 +101,12 @@ class LoginFormPresenter @Inject constructor( } } + fun onDomainSuffixChanged() { + view?.apply { + clearDomainSuffixError() + } + } + fun updateCustomDomainSuffixVisibility() { view?.run { showDomainSuffixInput("customSuffix" in formHostValue) @@ -159,7 +165,7 @@ class LoginFormPresenter @Inject constructor( fun onSignInClick() { val loginData = getLoginData() - if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return + if (!validateCredentials(loginData)) return resourceFlow { studentRepository.getUserSubjectsFromScrapper( @@ -229,24 +235,29 @@ class LoginFormPresenter @Inject constructor( view?.onRecoverClick() } - private fun validateCredentials(login: String, password: String, host: String): Boolean { + private fun validateCredentials(loginData: LoginData): Boolean { var isCorrect = true - if (login.isEmpty()) { + if (loginData.login.isEmpty()) { view?.setErrorUsernameRequired() isCorrect = false } else { - if ("@" in login && "login" in host) { + if ("@" in loginData.login && "login" in loginData.baseUrl) { view?.setErrorLoginRequired() isCorrect = false } - if ("@" !in login && "email" in host) { + if ("@" !in loginData.login && "email" in loginData.baseUrl) { view?.setErrorEmailRequired() isCorrect = false } - if ("@" in login && "||" !in login && "login" !in host && "email" !in host) { - val emailHost = login.substringAfter("@") - val emailDomain = URL(host).host + + val isEmailLogin = "@" in loginData.login + val isEmailWithLogin = "||" !in loginData.login + val isLoginNotRequired = "login" !in loginData.baseUrl + val isEmailNotRequired = "email" !in loginData.baseUrl + if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) { + val emailHost = loginData.login.substringAfter("@") + val emailDomain = URL(loginData.baseUrl).host if (!emailHost.equals(emailDomain, true)) { view?.setErrorEmailInvalid(domain = emailDomain) isCorrect = false @@ -254,16 +265,21 @@ class LoginFormPresenter @Inject constructor( } } - if (password.isEmpty()) { + if (loginData.password.isEmpty()) { view?.setErrorPassRequired(focus = isCorrect) isCorrect = false } - if (password.length < 6 && password.isNotEmpty()) { + if (loginData.password.length < 6 && loginData.password.isNotEmpty()) { view?.setErrorPassInvalid(focus = isCorrect) isCorrect = false } + if (loginData.domainSuffix !in listOf("", "rc", "kurs")) { + view?.setDomainSuffixInvalid() + isCorrect = false + } + return isCorrect } } 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 f2b7b1003..6ea22d180 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 @@ -46,12 +46,16 @@ interface LoginFormView : BaseView { fun setErrorEmailInvalid(domain: String) + fun setDomainSuffixInvalid() + fun clearUsernameError() fun clearPassError() fun clearHostError() + fun clearDomainSuffixError() + fun showSoftKeyboard() fun hideSoftKeyboard() 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 cc88b09e9..5c31f14d4 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 @@ -96,10 +96,7 @@ class LoginSymbolPresenter @Inject constructor( ?.takeIf { it.symbol == loginData.userEnteredSymbol } if (enteredSymbolDetails?.error is InvalidSymbolException) { - view?.run { - setErrorSymbolInvalid() - showContact(true) - } + showInvalidSymbolError() } else { Timber.i("Login with symbol result: Success") view?.navigateToStudentSelect(loginData, requireNotNull(user.data)) @@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor( loginErrorHandler.dispatch(user.error) lastError = user.error view?.showContact(true) + if (user.error is InvalidSymbolException) { + showInvalidSymbolError() + } } } }.onResourceNotLoading { @@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor( return normalizedSymbol in definitelyInvalidSymbols } + private fun showInvalidSymbolError() { + view?.run { + setErrorSymbolInvalid() + showContact(true) + } + } + fun onFaqClick() { view?.openFaqPage() } diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index fc5e5f35e..10864e640 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -261,6 +261,7 @@ android:layout_marginEnd="24dp" android:layout_marginRight="24dp" android:hint="@string/login_domain_suffix_hint" + app:errorEnabled="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1fa3ce73..0a4dcf7f4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,6 +61,7 @@ Invalid email Use the assigned login instead of email Use the assigned login or email in @%1$s + Invalid domain suffix Invalid symbol. If you cannot find it, please contact the school Don\'t make this up! If you cannot find it, please contact the school Student not found. Validate the symbol and the chosen variation of the UONET+ register From 736d16a7ab475c86c5306df17de5be8457533620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 17 Feb 2024 12:31:14 +0100 Subject: [PATCH 392/545] Make WebkitCookieManagerProxy no-op if webview is not available on the device (#2427) --- .../utils/WebkitCookieManagerProxy.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt index a54978717..3d41c711c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.utils +import android.util.AndroidRuntimeException import java.net.CookiePolicy import java.net.CookieStore import java.net.HttpCookie @@ -9,7 +10,18 @@ import java.net.CookieManager as JavaCookieManager class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { - private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance() + private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager() + + /** + * @see [https://stackoverflow.com/a/70354583/6695449] + */ + private fun getWebkitCookieManager(): WebkitCookieManager? { + return try { + WebkitCookieManager.getInstance() + } catch (e: AndroidRuntimeException) { + null + } + } override fun put(uri: URI?, responseHeaders: Map>?) { if (uri == null || responseHeaders == null) return @@ -23,7 +35,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL // process each of the headers for (headerValue in responseHeaders[headerKey].orEmpty()) { - webkitCookieManager.setCookie(url, headerValue) + webkitCookieManager?.setCookie(url, headerValue) } } } @@ -34,7 +46,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL ): Map> { require(!(uri == null || requestHeaders == null)) { "Argument is null" } val res = mutableMapOf>() - val cookie = webkitCookieManager.getCookie(uri.toString()) + val cookie = webkitCookieManager?.getCookie(uri.toString()) if (cookie != null) res["Cookie"] = listOf(cookie) return res } @@ -50,7 +62,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL cookies.remove(uri, cookie) override fun removeAll(): Boolean { - webkitCookieManager.removeAllCookies(null) + webkitCookieManager?.removeAllCookies(null) ?: return false return true } } From e757585bd38030e09282d1ce6ceb0d083298e6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 17 Feb 2024 12:43:52 +0100 Subject: [PATCH 393/545] New Crowdin updates (#2426) --- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-de/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 + 6 files changed, 6 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 85a67f9b3..e1cafa6ea 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -56,6 +56,7 @@ Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Použijte přiřazené přihlašovací nebo e-mail v @%1$s + Invalid domain suffix Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0fbba392c..5bd71bb29 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -56,6 +56,7 @@ Ungültige email Den zugewiesenen Login anstelle von email verwenden Benutze den zugewiesenen Login oder E-Mail in @%1$s + Invalid domain suffix Invalid symbol. If you cannot find it, please contact the school Don\'t make this up! If you cannot find it, please contact the school Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index df9ef8dbd..70d4982b9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -56,6 +56,7 @@ Nieprawidłowy adres e-mail Użyj loginu zamiast adresu e-mail Użyj loginu lub adresu e-mail w @%1$s + Nieprawidłowy sufiks domeny Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+ diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fffc5ce1e..717e02131 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -56,6 +56,7 @@ Неверный e-mail Используйте назначенный логин вместо e-mail Используйте назначенный логин или email в @%1$s + Invalid domain suffix Invalid symbol. If you cannot find it, please contact the school Don\'t make this up! If you cannot find it, please contact the school Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+ diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 1e822890b..368ead9d5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -56,6 +56,7 @@ Neplatný e-mail Namiesto e-mailu použite priradené prihlasovacie údaje Použite priradené prihlasovacie alebo e-mail v @%1$s + Invalid domain suffix Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 47034de62..3d10f1179 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -56,6 +56,7 @@ Недійсна адреса e-mail Використовуйте призначений логін замість адреси e-mail Використовуйте призначений логін або адресу e-mail в @%1$s + Invalid domain suffix Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+ From 3cf6c295b01512f6acabd27e864d821c3f5a79cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 17 Feb 2024 13:07:45 +0100 Subject: [PATCH 394/545] Version 2.4.1 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f40b74dbb..e6d35f548 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 146 - versionName "2.4.0" + versionCode 147 + versionName "2.4.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.4.1-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.4.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 012bbd26d..5736992bf 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,5 @@ -Wersja 2.4.0 +Wersja 2.4.1 -— naprawiliśmy logowanie do aplikacji na odmianie standardowej -— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start -— dodaliśmy oceny opisowe -— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność +- drobne poprawki stabilności aplikacji i odświeżania danych Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 7d8be1b9fc63e7e232ea93754abd385a62bd825c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:21:38 +0000 Subject: [PATCH 395/545] Bump coroutines from 1.7.3 to 1.8.0 (#2428) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e6d35f548..0b9512cb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,7 @@ ext { room = "2.6.1" chucker = "4.0.0" mockk = "1.13.9" - coroutines = "1.7.3" + coroutines = "1.8.0" } dependencies { From cfec79405fe8c683012ff5583ccbe508bee502c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:21:59 +0000 Subject: [PATCH 396/545] Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2429) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0b9512cb9..1408fedf1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -199,7 +199,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation 'androidx.core:core-ktx:1.12.0' From ec101c1f52a6ea150d52484314cf66003891c0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 19 Feb 2024 11:30:05 +0100 Subject: [PATCH 397/545] Fix error handling in widgets (#2430) --- .../java/io/github/wulkanowy/data/Resource.kt | 11 +++- .../LuckyNumberWidgetProvider.kt | 15 ++--- .../timetablewidget/TimetableWidgetFactory.kt | 59 +++++++++++++------ .../timetablewidget/TimetableWidgetItem.kt | 5 ++ .../layout/item_widget_timetable_error.xml | 12 ++++ app/src/main/res/layout/widget_timetable.xml | 2 - 6 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 app/src/main/res/layout/item_widget_timetable_error.xml 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 d7c2aeed9..108b0d58e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -30,8 +30,15 @@ val Resource.dataOrNull: T? get() = when (this) { is Resource.Success -> this.data is Resource.Intermediate -> this.data - is Resource.Loading -> null - is Resource.Error -> null + else -> null + } + +val Resource.dataOrThrow: T + get() = when (this) { + is Resource.Success -> this.data + is Resource.Intermediate -> this.data + is Resource.Loading -> throw IllegalStateException("Resource is in loading state") + is Resource.Error -> throw this.error } val Resource.errorOrNull: Throwable? diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index bafb2d7e5..1ab079a3a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -11,10 +11,8 @@ import android.view.View import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.dataOrThrow import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.toFirstResult @@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { appWidgetIds?.forEach { widgetId -> val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) - val luckyNumberResource = getLuckyNumber(studentId, widgetId) - val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString() + val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString() val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) .apply { setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-") @@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) } } + else -> null } if (currentStudent != null) { luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false) .toFirstResult() - } else { - Resource.Success(null) - } + .dataOrThrow + } else null } catch (e: Exception) { Timber.e(e, "An error has occurred in lucky number provider") - Resource.Error(e) + null } } 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 4e0578e2b..4cfc03229 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 @@ -11,7 +11,7 @@ import android.view.View.VISIBLE import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R -import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.dataOrThrow import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -27,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.getErrorString import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking @@ -67,25 +68,31 @@ class TimetableWidgetFactory( override fun onDestroy() {} override fun onDataSetChanged() { - intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId -> - val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) - val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) + val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return + val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) + val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) + items = emptyList() + + runBlocking { runCatching { - runBlocking { - val student = getStudent(studentId) ?: return@runBlocking - val semester = semesterRepository.getCurrentSemester(student) - items = createItems( - lessons = getLessons(student, semester, date), - lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) - ) + val student = getStudent(studentId) ?: return@runBlocking + val semester = semesterRepository.getCurrentSemester(student) + val lessons = getLessons(student, semester, date) + val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) + + createItems(lessons, lastSync) + } + .onFailure { + items = listOf(TimetableWidgetItem.Error(it)) + Timber.e(it, "An error has occurred in timetable widget factory") + } + .onSuccess { + items = it if (date == LocalDate.now()) { updateTodayLastLessonEnd(appWidgetId) } } - }.onFailure { - Timber.e(it, "An error has occurred in timetable widget factory") - } } } @@ -98,7 +105,7 @@ class TimetableWidgetFactory( student: Student, semester: Semester, date: LocalDate ): List { val timetable = timetableRepository.getTimetable(student, semester, date, date, false) - val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty() + val lessons = timetable.toFirstResult().dataOrThrow.lessons return lessons.sortedBy { it.number } } @@ -110,6 +117,7 @@ class TimetableWidgetFactory( BETWEEN_AND_BEFORE_LESSONS -> 0 else -> null } + return buildList { lessons.forEach { if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) { @@ -133,15 +141,12 @@ class TimetableWidgetFactory( sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) } - companion object { - const val TIME_FORMAT_STYLE = "HH:mm" - } - override fun getViewAt(position: Int): RemoteViews? { return when (val item = items.getOrNull(position) ?: return null) { is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item) is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item) is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item) + is TimetableWidgetItem.Error -> getErrorItemRemoteView(item) } } @@ -213,6 +218,18 @@ class TimetableWidgetFactory( } } + private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews { + return RemoteViews( + context.packageName, + R.layout.item_widget_timetable_error + ).apply { + setTextViewText( + R.id.timetable_widget_item_error_message, + context.resources.getErrorString(item.error) + ) + } + } + private fun updateTheme() { when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> { @@ -300,4 +317,8 @@ class TimetableWidgetFactory( synchronizationTime, ) } + + private companion object { + private const val TIME_FORMAT_STYLE = "HH:mm" + } } 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 index 166b1a8fb..fb02f8919 100644 --- 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 @@ -17,10 +17,15 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) { data class Synchronized( val timestamp: Instant, ) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED) + + data class Error( + val error: Throwable + ) : TimetableWidgetItem(TimetableWidgetItemType.ERROR) } enum class TimetableWidgetItemType { NORMAL, EMPTY, SYNCHRONIZED, + ERROR, } diff --git a/app/src/main/res/layout/item_widget_timetable_error.xml b/app/src/main/res/layout/item_widget_timetable_error.xml new file mode 100644 index 000000000..6f9ab067a --- /dev/null +++ b/app/src/main/res/layout/item_widget_timetable_error.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml index b07cc78f6..b438da6c3 100644 --- a/app/src/main/res/layout/widget_timetable.xml +++ b/app/src/main/res/layout/widget_timetable.xml @@ -114,7 +114,5 @@ android:text="@string/widget_timetable_no_items" android:textAppearance="?attr/textAppearanceBody1" android:visibility="gone" /> - - From 729e72cddb4c9b04e509914a66e654b9608db3df Mon Sep 17 00:00:00 2001 From: Kacper Majcher Date: Wed, 21 Feb 2024 21:36:20 +0100 Subject: [PATCH 398/545] Fix text pasting into date field in additional lesson add dialog (#2433) --- app/src/main/res/layout/dialog_additional_add.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/dialog_additional_add.xml b/app/src/main/res/layout/dialog_additional_add.xml index 4be436d76..78967394b 100644 --- a/app/src/main/res/layout/dialog_additional_add.xml +++ b/app/src/main/res/layout/dialog_additional_add.xml @@ -39,7 +39,7 @@ android:layout_height="wrap_content" android:editable="false" android:focusable="false" - android:inputType="text" + android:inputType="none" tools:ignore="Deprecated" /> @@ -67,7 +67,7 @@ android:layout_height="wrap_content" android:editable="false" android:focusable="false" - android:inputType="text" + android:inputType="none" tools:ignore="Deprecated" /> @@ -87,7 +87,7 @@ android:layout_height="wrap_content" android:editable="false" android:focusable="false" - android:inputType="text" + android:inputType="none" tools:ignore="Deprecated" /> From 2776d019b957062b87a31f93ff6a282f5548124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 22 Feb 2024 15:52:40 +0100 Subject: [PATCH 399/545] =?UTF-8?q?Revert=20"Bump=20com.google.android.ump?= =?UTF-8?q?:user-messaging-platform=20from=202.1.0=20to=202.2=E2=80=A6"=20?= =?UTF-8?q?(#2434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fc91936884715cc1d46d5af17086364961f8631d. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1408fedf1..c3a7baabd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -262,7 +262,7 @@ dependencies { playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' - playImplementation "com.google.android.ump:user-messaging-platform:2.2.0" + playImplementation "com.google.android.ump:user-messaging-platform:2.1.0" hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' From b613b844692580874099e0d205919fb11b1d7b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 22 Feb 2024 16:15:24 +0100 Subject: [PATCH 400/545] Version 2.4.2 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c3a7baabd..26c2547e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 147 - versionName "2.4.1" + versionCode 148 + versionName "2.4.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,8 +164,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.50d - updatePriority = 1 + userFraction = 0.99d + updatePriority = 2 enabled.set(false) } 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 5736992bf..ef6308b6c 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,7 @@ -Wersja 2.4.1 +Wersja 2.4.2 -- drobne poprawki stabilności aplikacji i odświeżania danych +- naprawiliśmy crash przy przełączaniu uczniów, motywów i języków +- naprawiliśmy crash przy dodawaniu dodatkowych lekcji +- naprawiliśmy obsługę błędów widżetach Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 31854fc4b86f3b66f63720709d423a62fa13b2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 25 Feb 2024 16:35:56 +0100 Subject: [PATCH 401/545] Fix text cut off across the app when text size is set to 200% (#2435) --- app/src/main/res/layout/activity_main.xml | 2 +- .../main/res/layout/dialog_account_edit.xml | 6 ++-- .../main/res/layout/dialog_additional_add.xml | 6 ++-- app/src/main/res/layout/dialog_attendance.xml | 3 +- app/src/main/res/layout/dialog_conference.xml | 3 +- app/src/main/res/layout/dialog_exam.xml | 6 ++-- app/src/main/res/layout/dialog_grade.xml | 3 +- app/src/main/res/layout/dialog_homework.xml | 6 ++-- .../main/res/layout/dialog_homework_add.xml | 6 ++-- .../res/layout/dialog_lesson_completed.xml | 3 +- .../main/res/layout/dialog_mobile_device.xml | 18 +++++----- app/src/main/res/layout/dialog_note.xml | 3 +- .../res/layout/dialog_school_announcement.xml | 3 +- app/src/main/res/layout/dialog_timetable.xml | 3 +- .../main/res/layout/header_grade_details.xml | 15 +++++++++ app/src/main/res/layout/item_timetable.xml | 33 ++++++++++++------- .../layout/subitem_dashboard_small_grade.xml | 4 +++ 17 files changed, 86 insertions(+), 37 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d14de50a1..a9284234e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -16,7 +16,7 @@ + android:layout_height="wrap_content" /> diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml index 8c6cf0a76..10b719077 100644 --- a/app/src/main/res/layout/dialog_homework.xml +++ b/app/src/main/res/layout/dialog_homework.xml @@ -27,7 +27,7 @@ android:id="@+id/homeworkDialogRead" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginEnd="8dp" android:layout_marginBottom="24dp" @@ -35,6 +35,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/homework_mark_as_done" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/homeworkDialogClose" /> @@ -43,13 +44,14 @@ android:id="@+id/homeworkDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginEnd="24dp" android:layout_marginBottom="24dp" android:insetLeft="0dp" android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml index e0ff5b749..dc7ae32d5 100644 --- a/app/src/main/res/layout/dialog_homework_add.xml +++ b/app/src/main/res/layout/dialog_homework_add.xml @@ -94,7 +94,7 @@ android:id="@+id/homeworkDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginTop="24dp" android:layout_marginEnd="8dp" @@ -103,6 +103,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/homeworkDialogAdd" @@ -112,13 +113,14 @@ android:id="@+id/homeworkDialogAdd" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginBottom="24dp" android:insetLeft="0dp" android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml index 3a1d3fd00..fc32a252a 100644 --- a/app/src/main/res/layout/dialog_lesson_completed.xml +++ b/app/src/main/res/layout/dialog_lesson_completed.xml @@ -212,7 +212,7 @@ android:id="@+id/completedLessonDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginEnd="16dp" android:layout_marginBottom="24dp" @@ -220,6 +220,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/dialog_mobile_device.xml b/app/src/main/res/layout/dialog_mobile_device.xml index 9b81737fb..c526ed74c 100644 --- a/app/src/main/res/layout/dialog_mobile_device.xml +++ b/app/src/main/res/layout/dialog_mobile_device.xml @@ -18,10 +18,10 @@ android:layout_marginTop="24dp" android:adjustViewBounds="true" android:contentDescription="@string/mobile_device_qr" - tools:src="@tools:sample/avatars" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:src="@tools:sample/avatars" /> + + app:constraint_referenced_ids="mobileDeviceQr,mobileDeviceDialogTokenTitle,mobileDeviceDialogTokenValue,mobileDeviceDialogSymbolTitle,mobileDeviceDialogSymbolValue,mobileDeviceDialogPinTitle,mobileDeviceDialogPinValue,mobileDeviceDialogClose" + tools:visibility="visible" /> + tools:visibility="invisible" /> diff --git a/app/src/main/res/layout/dialog_note.xml b/app/src/main/res/layout/dialog_note.xml index 9c8b18b32..3b88ea5f8 100644 --- a/app/src/main/res/layout/dialog_note.xml +++ b/app/src/main/res/layout/dialog_note.xml @@ -180,7 +180,7 @@ android:id="@+id/noteDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginEnd="16dp" android:layout_marginBottom="24dp" @@ -188,6 +188,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/dialog_school_announcement.xml b/app/src/main/res/layout/dialog_school_announcement.xml index 4e0ef556f..a771b772f 100644 --- a/app/src/main/res/layout/dialog_school_announcement.xml +++ b/app/src/main/res/layout/dialog_school_announcement.xml @@ -122,7 +122,7 @@ android:id="@+id/announcementDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginEnd="16dp" android:layout_marginBottom="24dp" @@ -130,6 +130,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/dialog_timetable.xml b/app/src/main/res/layout/dialog_timetable.xml index aeb01b3ba..de2696482 100644 --- a/app/src/main/res/layout/dialog_timetable.xml +++ b/app/src/main/res/layout/dialog_timetable.xml @@ -263,7 +263,7 @@ android:id="@+id/timetableDialogClose" style="@style/Widget.Material3.Button.TextButton.Dialog" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginEnd="16dp" android:layout_marginBottom="24dp" @@ -271,6 +271,7 @@ android:insetTop="0dp" android:insetRight="0dp" android:insetBottom="0dp" + android:minHeight="36dp" android:text="@string/all_close" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml index f2ba9a8c9..e43e8993f 100644 --- a/app/src/main/res/layout/header_grade_details.xml +++ b/app/src/main/res/layout/header_grade_details.xml @@ -45,6 +45,9 @@ android:textColor="?android:textColorSecondary" android:textSize="12sp" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject" app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject" tools:text="Average: 6,00" /> @@ -55,8 +58,12 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginTop="5dp" + android:ellipsize="end" + android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="12sp" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber" app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage" app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject" tools:text="Points: 123/200 (61,5%)" /> @@ -67,8 +74,13 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginTop="5dp" + android:layout_marginEnd="8dp" + android:ellipsize="end" + android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="12sp" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/gradeHeaderPointsSum" app:layout_constraintTop_toBottomOf="@id/gradeHeaderSubject" tools:text="12 grades" /> @@ -85,6 +97,9 @@ android:paddingRight="5dp" android:textColor="?colorOnPrimary" android:textSize="14sp" + app:autoSizeMaxTextSize="16dp" + app:autoSizeMinTextSize="10dp" + app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml index 57af6f7ea..b9966c121 100644 --- a/app/src/main/res/layout/item_timetable.xml +++ b/app/src/main/res/layout/item_timetable.xml @@ -1,7 +1,6 @@ @@ -49,8 +49,9 @@ android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="13sp" + app:layout_constraintBottom_toTopOf="@id/timetableItemTimeFinish" app:layout_constraintStart_toEndOf="@id/timetableItemNumber" - app:layout_constraintTop_toTopOf="@id/timetableItemNumber" + app:layout_constraintTop_toTopOf="parent" tools:text="11:11" /> @@ -83,13 +91,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginTop="0dp" - android:layout_marginEnd="5dp" + android:layout_marginEnd="0dp" + android:ellipsize="end" + android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="13sp" app:layout_constraintEnd_toStartOf="@+id/timetableItemTeacher" app:layout_constraintStart_toEndOf="@+id/timetableItemRoom" - app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish" + app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish" tools:text="(2/2)" tools:visibility="visible" /> @@ -98,13 +107,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorSecondary" android:textSize="13sp" - app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/timetableItemGroup" + app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish" tools:text="Agata Kowalska - Błaszczyk" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml index 6800b72e9..3684c2677 100644 --- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml +++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml @@ -1,5 +1,6 @@ From e378b4c70adc8b4e4be7f302c51a16c9377066cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 25 Feb 2024 16:36:50 +0100 Subject: [PATCH 402/545] Fix loading timetable and attendance when should be refreshed returns true (#2436) --- .../wulkanowy/data/db/dao/TimetableDao.kt | 2 +- .../data/repositories/AttendanceRepository.kt | 10 +++------ .../data/repositories/TimetableRepository.kt | 22 ++++++++++++++----- .../IsStudentHasLessonsOnWeekendUseCase.kt | 11 ++-------- .../services/sync/works/TimetableWork.kt | 4 +--- .../modules/attendance/AttendancePresenter.kt | 18 ++++++--------- .../modules/dashboard/DashboardPresenter.kt | 2 +- .../modules/timetable/TimetablePresenter.kt | 7 +++--- 8 files changed, 34 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index b4b7379f2..40d97ea96 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -15,5 +15,5 @@ interface TimetableDao : BaseDao { fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List + suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 6d782047b..bbf627de0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -16,10 +16,8 @@ import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withContext import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime @@ -58,11 +56,9 @@ class AttendanceRepository @Inject constructor( attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) }, fetch = { - val lessons = withContext(Dispatchers.IO) { - timetableDb.load( - semester.diaryId, semester.studentId, start.monday, end.sunday - ) - } + val lessons = timetableDb.load( + semester.diaryId, semester.studentId, start.monday, end.sunday + ) sdk.init(student) .switchSemester(semester) .getAttendance(start.monday, end.sunday) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 9305d3b31..acbd02d18 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -3,13 +3,23 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao -import io.github.wulkanowy.data.db.entities.* +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.db.entities.TimetableAdditional +import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.sync.Mutex @@ -121,12 +131,12 @@ class TimetableRepository @Inject constructor( } } - fun getTimetableFromDatabase( + suspend fun getTimetableFromDatabase( semester: Semester, - from: LocalDate, + start: LocalDate, end: LocalDate - ): Flow> { - return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end) + ): List { + return timetableDb.load(semester.diaryId, semester.studentId, start, end) } suspend fun updateTimetable(timetable: List) { diff --git a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt index efe928e2b..ffd005740 100644 --- a/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt +++ b/app/src/main/java/io/github/wulkanowy/domain/timetable/IsStudentHasLessonsOnWeekendUseCase.kt @@ -1,10 +1,7 @@ package io.github.wulkanowy.domain.timetable -import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TimetableRepository -import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import java.time.LocalDate @@ -16,18 +13,14 @@ class IsStudentHasLessonsOnWeekendUseCase @Inject constructor( ) { suspend operator fun invoke( - student: Student, semester: Semester, currentDate: LocalDate = LocalDate.now(), ): Boolean { - val lessons = timetableRepository.getTimetable( - student = student, + val lessons = timetableRepository.getTimetableFromDatabase( semester = semester, start = currentDate.monday, end = currentDate.sunday, - forceRefresh = false, - timetableType = TimetableRepository.TimetableType.NORMAL - ).toFirstResult().dataOrNull?.lessons.orEmpty() + ) return isWeekendHasLessonsUseCase(lessons) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index ac9a8eb4c..2d10d925c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -6,7 +6,6 @@ import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay -import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject @@ -31,10 +30,9 @@ class TimetableWork @Inject constructor( timetableRepository.getTimetableFromDatabase( semester = semester, - from = startDate, + start = startDate, end = endDate, ) - .first() .filterNot { it.isNotified } .let { if (it.isNotEmpty()) changeTimetableNotification.notify(it, student) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index f66479daf..82fe69cb7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -4,18 +4,14 @@ import android.annotation.SuppressLint import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Attendance 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.repositories.AttendanceRepository 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 kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.DayOfWeek @@ -210,7 +206,7 @@ class AttendancePresenter @Inject constructor( val semester = semesterRepository.getCurrentSemester(student) - checkInitialAndCurrentDate(student, semester) + checkInitialAndCurrentDate(semester) attendanceRepository.getAttendance( student = student, semester = semester, @@ -266,15 +262,13 @@ class AttendancePresenter @Inject constructor( .launch() } - private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { + private suspend fun checkInitialAndCurrentDate(semester: Semester) { if (initialDate == null) { - val lessons = attendanceRepository.getAttendance( - student = student, + val lessons = attendanceRepository.getAttendanceFromDatabase( semester = semester, start = now().monday, end = now().sunday, - forceRefresh = false, - ).toFirstResult().dataOrNull.orEmpty() + ).firstOrNull().orEmpty() isWeekendHasLessons = isWeekendHasLessons(lessons) initialDate = getInitialDate(semester) } @@ -316,6 +310,7 @@ class AttendancePresenter @Inject constructor( showContent(false) showExcuseButton(false) } + is Resource.Success -> { Timber.i("Excusing for absence result: Success") analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) @@ -328,6 +323,7 @@ class AttendancePresenter @Inject constructor( } loadData(forceRefresh = true) } + is Resource.Error -> { Timber.i("Excusing for absence result: An exception occurred") errorHandler.dispatch(it.error) 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 1e6f1c198..784ac112f 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 @@ -438,7 +438,7 @@ class DashboardPresenter @Inject constructor( private fun loadLessons(student: Student, forceRefresh: Boolean) { flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) - val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) { + val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) { true -> LocalDate.now() else -> LocalDate.now().nextOrSameSchoolDay } 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 7e8c876ef..e83f25176 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 @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.timetable import android.os.Handler import android.os.Looper 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 @@ -150,7 +149,7 @@ class TimetablePresenter @Inject constructor( val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - checkInitialAndCurrentDate(student, semester) + checkInitialAndCurrentDate(semester) timetableRepository.getTimetable( student = student, semester = semester, @@ -194,9 +193,9 @@ class TimetablePresenter @Inject constructor( .launch() } - private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) { + private suspend fun checkInitialAndCurrentDate(semester: Semester) { if (initialDate == null) { - isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester) + isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester) initialDate = getInitialDate(semester) } From d5c17285c1ce29c87e3f28cf380b8691d7bb468a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 25 Feb 2024 16:37:28 +0100 Subject: [PATCH 403/545] Fix error handling in login (#2437) --- app/build.gradle | 2 +- .../main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt | 6 +++++- .../io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt | 6 +++++- .../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 4 ++++ .../java/io/github/wulkanowy/utils/ExceptionExtension.kt | 2 ++ app/src/main/res/values/api_hosts.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 7 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 26c2547e5..b81236672 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.4.1' + implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index e17c0c9ec..7109f1ffd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -34,7 +34,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co } protected open fun proceed(error: Throwable) { - showErrorMessage(context.resources.getErrorString(error), error) + showDefaultMessage(error) when (error) { is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl) is ScramblerException -> onDecryptionFailed() @@ -45,6 +45,10 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co } } + fun showDefaultMessage(error: Throwable) { + showErrorMessage(context.resources.getErrorString(error), error) + } + open fun clear() { showErrorMessage = { _, _ -> } onExpiredCredentials = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt index 8f579712b..3c061f498 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt @@ -62,7 +62,11 @@ class AuthPresenter @Inject constructor( } isSuccess } - .onFailure { errorHandler.dispatch(it) } + .onFailure { + errorHandler.dispatch(it) + view?.showProgress(false) + view?.showContent(true) + } .onSuccess { if (it) { view?.showSuccess(true) 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 69e1d027d..39bc3f02d 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 @@ -14,6 +14,7 @@ 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.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler @@ -204,6 +205,9 @@ class LoginFormPresenter @Inject constructor( } .onResourceError { loginErrorHandler.dispatch(it) + if (it is InvalidSymbolException) { + loginErrorHandler.showDefaultMessage(it) + } lastError = it view?.showContact(true) analytics.logEvent( diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt index 18fc10bba..1c2290510 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.content.res.Resources import io.github.wulkanowy.R import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.AccountInactiveException import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException @@ -33,6 +34,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) { is ServiceUnavailableException -> R.string.error_service_unavailable is FeatureDisabledException -> R.string.error_feature_disabled is FeatureNotAvailableException -> R.string.error_feature_not_available + is AccountInactiveException -> R.string.error_account_inactive is VulcanException -> R.string.error_unknown_uonet is ScrapperException -> R.string.error_unknown_app is CloudflareVerificationException -> R.string.error_cloudflare_captcha diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 6439b462f..9768329d0 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -66,7 +66,7 @@ gminaulanmajorat gminaozorkow gminalopiennikgorny - warszawa + saas1 powiatwulkanowy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a4dcf7f4..faed4d186 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -852,6 +852,7 @@ No internet connection An error occurred. Check your device clock + This account is inactive. Try logging in again Connection to register failed. Servers can be overloaded. Please try again later Loading data failed. Please try again later Register password change required From 74a20b2f65cb7af7be333fba86990f3961d94643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 27 Feb 2024 09:42:44 +0100 Subject: [PATCH 404/545] Add Github Sponsor (#2444) --- .github/FUNDING.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..cdce0759b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: wulkanowy +custom: https://www.paypal.com/paypalme/wulkanowy From 1b8c3899842505a4f1616fac8ce7b550afb602ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:52:47 +0000 Subject: [PATCH 405/545] Bump io.coil-kt:coil from 2.5.0 to 2.6.0 (#2441) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b81236672..e88d9205d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,7 +246,7 @@ dependencies { implementation 'com.github.Faierbel:slf4j-timber:2.0' implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation "com.mikepenz:aboutlibraries-core:$about_libraries" - implementation 'io.coil-kt:coil:2.5.0' + implementation 'io.coil-kt:coil:2.6.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 1ab300d74f4ea41c395f4db1d7b8f926e363b3c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:53:00 +0000 Subject: [PATCH 406/545] Bump android_hilt from 1.1.0 to 1.2.0 (#2443) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e88d9205d..07efeb2f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ huaweiPublish { ext { work_manager = "2.9.0" - android_hilt = "1.1.0" + android_hilt = "1.2.0" room = "2.6.1" chucker = "4.0.0" mockk = "1.13.9" From 7a4032dda4e3061a09ecffd69712b3598e82e414 Mon Sep 17 00:00:00 2001 From: JestemKamil <84380834+JestemKamil@users.noreply.github.com> Date: Thu, 29 Feb 2024 21:30:02 +0100 Subject: [PATCH 407/545] Add mute message senders (#2415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../60.json | 2527 +++++++++++++++++ app/src/main/assets/contributors.json | 4 + .../io/github/wulkanowy/data/DataModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 8 +- .../wulkanowy/data/db/dao/MessagesDao.kt | 10 +- .../data/db/dao/MutedMessageSendersDao.kt | 20 + .../data/db/entities/MessageWithAttachment.kt | 8 +- .../db/entities/MessageWithMutedAuthor.kt | 12 + .../data/db/entities/MutedMessageSender.kt | 15 + .../data/repositories/MessageRepository.kt | 35 +- .../modules/dashboard/DashboardPresenter.kt | 1 + .../message/preview/MessagePreviewAdapter.kt | 6 + .../message/preview/MessagePreviewFragment.kt | 18 +- .../preview/MessagePreviewPresenter.kt | 106 +- .../message/preview/MessagePreviewView.kt | 6 + .../modules/message/tab/MessageTabAdapter.kt | 16 +- .../modules/message/tab/MessageTabDataItem.kt | 1 + .../message/tab/MessageTabPresenter.kt | 38 +- .../res/drawable/ic_circle_notification.xml | 10 + .../res/drawable/ic_notifications_off.xml | 5 + app/src/main/res/layout/item_message.xml | 6 +- .../res/menu/action_menu_message_preview.xml | 7 + app/src/main/res/values/strings.xml | 6 + .../repositories/MessageRepositoryTest.kt | 47 +- 24 files changed, 2827 insertions(+), 89 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt create mode 100644 app/src/main/res/drawable/ic_circle_notification.xml create mode 100644 app/src/main/res/drawable/ic_notifications_off.xml diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json new file mode 100644 index 000000000..20eacad1c --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json @@ -0,0 +1,2527 @@ +{ + "formatVersion": 1, + "database": { + "version": 60, + "identityHash": "3672d3f4d5e6b874e5a22d2bb458dc65", + "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, '3672d3f4d5e6b874e5a22d2bb458dc65')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index a7629c22f..97ac9356f 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -54,5 +54,9 @@ { "displayName": "Antoni Paduch", "githubUsername": "janAte1" + }, + { + "displayName": "Kamil Wąsik", + "githubUsername": "JestemKamil" } ] diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 7c9cf9a3c..6b6c9d329 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -254,6 +254,10 @@ internal class DataModule { @Provides fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao + @Singleton + @Provides + fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao + @Singleton @Provides fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao 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 8e5841fe7..21a6e3f3e 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 @@ -25,6 +25,7 @@ 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.dao.MobileDeviceDao +import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.dao.NotificationDao import io.github.wulkanowy.data.db.dao.RecipientDao @@ -56,6 +57,7 @@ import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.MutedMessageSender import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.data.db.entities.Recipient @@ -157,6 +159,7 @@ import javax.inject.Singleton SchoolAnnouncement::class, Notification::class, AdminMessage::class, + MutedMessageSender::class, GradeDescriptive::class, ], autoMigrations = [ @@ -169,6 +172,7 @@ import javax.inject.Singleton AutoMigration(from = 56, to = 57, spec = Migration57::class), AutoMigration(from = 57, to = 58, spec = Migration58::class), AutoMigration(from = 58, to = 59), + AutoMigration(from = 59, to = 60), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -177,7 +181,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 59 + const val VERSION_SCHEMA = 60 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -303,5 +307,7 @@ abstract class AppDatabase : RoomDatabase() { abstract val adminMessagesDao: AdminMessageDao + abstract val mutedMessageSendersDao: MutedMessageSendersDao + abstract val gradeDescriptiveDao: GradeDescriptiveDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt index 1709f7636..11e6da1e7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MessagesDao.kt @@ -5,15 +5,23 @@ import androidx.room.Query import androidx.room.Transaction import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor import kotlinx.coroutines.flow.Flow @Dao interface MessagesDao : BaseDao { - @Transaction @Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey") fun loadMessageWithAttachment(messageGlobalKey: String): Flow + @Transaction + @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") + fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow> + + @Transaction + @Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC") + fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow> + @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") fun loadAll(mailboxKey: String, folder: Int): Flow> diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt new file mode 100644 index 000000000..0a8664010 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MutedMessageSendersDao.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.MutedMessageSender + +@Dao +interface MutedMessageSendersDao : BaseDao { + + @Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author") + suspend fun checkMute(author: String): Boolean + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertMute(mute: MutedMessageSender): Long + + @Query("DELETE FROM MutedMessageSenders WHERE author = :author") + suspend fun deleteMute(author: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt index cd468215d..fc890e760 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithAttachment.kt @@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities import androidx.room.Embedded import androidx.room.Relation +import java.io.Serializable data class MessageWithAttachment( @Embedded val message: Message, @Relation(parentColumn = "message_global_key", entityColumn = "message_global_key") - val attachments: List -) + val attachments: List, + + @Relation(parentColumn = "correspondents", entityColumn = "author") + val mutedMessageSender: MutedMessageSender?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt new file mode 100644 index 000000000..e3cd1ca7d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MessageWithMutedAuthor.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.Embedded +import androidx.room.Relation + +data class MessageWithMutedAuthor( + @Embedded + val message: Message, + + @Relation(parentColumn = "correspondents", entityColumn = "author") + val mutedMessageSender: MutedMessageSender?, +) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt new file mode 100644 index 000000000..f1770e64c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MutedMessageSender.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity(tableName = "MutedMessageSenders") +data class MutedMessageSender( + @ColumnInfo(name = "author") + val author: String, +) : Serializable { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} 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 c8fccb23d..6d591c5bb 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 @@ -8,9 +8,12 @@ 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.dao.MutedMessageSendersDao 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.MessageWithMutedAuthor +import io.github.wulkanowy.data.db.entities.MutedMessageSender import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder @@ -42,6 +45,7 @@ import javax.inject.Singleton @Singleton class MessageRepository @Inject constructor( private val messagesDb: MessagesDao, + private val mutedMessageSendersDao: MutedMessageSendersDao, private val messageAttachmentDao: MessageAttachmentDao, private val sdk: Sdk, @ApplicationContext private val context: Context, @@ -51,7 +55,6 @@ class MessageRepository @Inject constructor( private val mailboxDao: MailboxDao, private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase, ) { - private val saveFetchResultMutex = Mutex() private val messagesCacheKey = "message" @@ -63,7 +66,7 @@ class MessageRepository @Inject constructor( folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false, - ): Flow>> = networkBoundResource( + ): Flow>> = networkBoundResource( mutex = saveFetchResultMutex, isResultEmpty = { it.isEmpty() }, shouldFetch = { @@ -74,8 +77,8 @@ class MessageRepository @Inject constructor( }, query = { if (mailbox == null) { - messagesDb.loadAll(folder.id, student.email) - } else messagesDb.loadAll(mailbox.globalKey, folder.id) + messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email) + } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id) }, fetch = { sdk.init(student).getMessages( @@ -83,10 +86,12 @@ class MessageRepository @Inject constructor( mailboxKey = mailbox?.globalKey, ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email)) }, - saveFetchResult = { old, new -> + saveFetchResult = { oldWithAuthors, new -> + val old = oldWithAuthors.map { it.message } messagesDb.deleteAll(old uniqueSubtract new) messagesDb.insertAll((new uniqueSubtract old).onEach { - it.isNotified = !notify + val muted = isMuted(it.correspondents) + it.isNotified = !notify || muted }) refreshHelper.updateLastRefreshTimestamp( @@ -106,9 +111,7 @@ class MessageRepository @Inject constructor( Timber.d("Message content in db empty: ${it.message.content.isBlank()}") (it.message.unread && markAsRead) || it.message.content.isBlank() }, - query = { - messagesDb.loadMessageWithAttachment(message.messageGlobalKey) - }, + query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, fetch = { sdk.init(student).getMessageDetails( messageKey = it!!.message.messageGlobalKey, @@ -236,4 +239,18 @@ class MessageRepository @Inject constructor( context.getString(R.string.pref_key_message_draft), value?.let { json.encodeToString(it) } ) + + suspend fun isMuted(author: String): Boolean { + return mutedMessageSendersDao.checkMute(author) + } + + suspend fun muteMessage(author: String) { + if (isMuted(author)) return + mutedMessageSendersDao.insertMute(MutedMessageSender(author)) + } + + suspend fun unmuteMessage(author: String) { + if (!isMuted(author)) return + mutedMessageSendersDao.deleteMute(author) + } } 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 784ac112f..3fec62562 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 @@ -304,6 +304,7 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh ) } + .mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } } .onResourceError { errorHandler.dispatch(it) } .takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt index d3c6b95c7..b83f7e232 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewAdapter.kt @@ -50,12 +50,15 @@ class MessagePreviewAdapter @Inject constructor() : ViewType.MESSAGE.id -> MessageViewHolder( ItemMessagePreviewBinding.inflate(inflater, parent, false) ) + ViewType.DIVIDER.id -> DividerViewHolder( ItemMessageDividerBinding.inflate(inflater, parent, false) ) + ViewType.ATTACHMENT.id -> AttachmentViewHolder( ItemMessageAttachmentBinding.inflate(inflater, parent, false) ) + else -> throw IllegalStateException() } } @@ -66,6 +69,7 @@ class MessagePreviewAdapter @Inject constructor() : holder, requireNotNull(messageWithAttachment).message ) + is AttachmentViewHolder -> bindAttachment( holder, requireNotNull(messageWithAttachment).attachments[position - 2] @@ -82,9 +86,11 @@ class MessagePreviewAdapter @Inject constructor() : recipientCount > 1 -> { context.getString(R.string.message_read_by, message.readBy, recipientCount) } + message.readBy == 1 || (isReceived && !message.unread) -> { context.getString(R.string.message_read, context.getString(R.string.all_yes)) } + else -> context.getString(R.string.message_read, context.getString(R.string.all_no)) } 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 3ed685cd7..3b33bb51f 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 @@ -50,12 +50,20 @@ class MessagePreviewFragment : private var menuPrintButton: MenuItem? = null + private var menuMuteButton: MenuItem? = null + override val titleStringId: Int get() = R.string.message_title override val deleteMessageSuccessString: String get() = getString(R.string.message_delete_success) + override val muteMessageSuccessString: String + get() = getString(R.string.message_mute_success) + + override val unmuteMessageSuccessString: String + get() = getString(R.string.message_unmute_success) + override val messageNoSubjectString: String get() = getString(R.string.message_no_subject) @@ -106,6 +114,7 @@ class MessagePreviewFragment : menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) menuShareButton = menu.findItem(R.id.messagePreviewMenuShare) menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint) + menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute) presenter.onCreateOptionsMenu() menu.findItem(R.id.mainMenuAccount).isVisible = false @@ -118,6 +127,7 @@ class MessagePreviewFragment : R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() R.id.messagePreviewMenuShare -> presenter.onShare() R.id.messagePreviewMenuPrint -> presenter.onPrint() + R.id.messagePreviewMenuMute -> presenter.onMute() else -> false } } @@ -129,6 +139,11 @@ class MessagePreviewFragment : } } + override fun updateMuteToggleButton(isMuted: Boolean) { + menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute) + + } + override fun showProgress(show: Boolean) { binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE } @@ -143,6 +158,7 @@ class MessagePreviewFragment : menuDeleteButton?.isVisible = show menuShareButton?.isVisible = show menuPrintButton?.isVisible = show + menuMuteButton?.isVisible = show && isReplayable } override fun setDeletedOptionsLabels() { @@ -213,7 +229,7 @@ class MessagePreviewFragment : } override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(MESSAGE_ID_KEY, presenter.message) + outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments) super.onSaveInstanceState(outState) } 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 cd7b72843..2eff245ff 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 @@ -5,7 +5,7 @@ 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.db.entities.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -26,9 +26,7 @@ class MessagePreviewPresenter @Inject constructor( private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { - var message: Message? = null - - var attachments: List? = null + var messageWithAttachments: MessageWithAttachment? = null private lateinit var lastError: Throwable @@ -38,7 +36,6 @@ class MessagePreviewPresenter @Inject constructor( super.onAttachView(view) view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError - this.message = message loadData(requireNotNull(message)) } @@ -66,13 +63,12 @@ class MessagePreviewPresenter @Inject constructor( .logResourceStatus("message ${messageToLoad.messageId} preview") .onResourceData { if (it != null) { - message = it.message - attachments = it.attachments + messageWithAttachments = it view?.apply { setMessageWithAttachment(it) showContent(true) initOptions() - + updateMuteToggleButton(isMuted = it.mutedMessageSender != null) if (preferencesRepository.isIncognitoMode && it.message.unread) { showMessage(R.string.message_incognito_description) } @@ -83,8 +79,7 @@ class MessagePreviewPresenter @Inject constructor( popView() } } - } - .onResourceSuccess { + }.onResourceSuccess { if (it != null) { analytics.logEvent( "load_item", @@ -92,31 +87,28 @@ class MessagePreviewPresenter @Inject constructor( "length" to it.message.content.length ) } - } - .onResourceNotLoading { view?.showProgress(false) } - .onResourceError { + }.onResourceNotLoading { view?.showProgress(false) }.onResourceError { retryCallback = { onMessageLoadRetry(messageToLoad) } errorHandler.dispatch(it) - } - .launch() + }.launch() } fun onReply(): Boolean { - return if (message != null) { - view?.openMessageReply(message) + return if (messageWithAttachments?.message != null) { + view?.openMessageReply(messageWithAttachments?.message) true } else false } fun onForward(): Boolean { - return if (message != null) { - view?.openMessageForward(message) + return if (messageWithAttachments?.message != null) { + view?.openMessageForward(messageWithAttachments?.message) true } else false } fun onShare(): Boolean { - val message = message ?: return false + val message = messageWithAttachments?.message ?: return false val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() } val text = buildString { @@ -129,13 +121,15 @@ class MessagePreviewPresenter @Inject constructor( appendLine(message.content.parseAsHtml()) - if (!attachments.isNullOrEmpty()) { + if (!messageWithAttachments?.attachments.isNullOrEmpty()) { appendLine() appendLine("Załączniki:") - append(attachments.orEmpty().joinToString(separator = "\n") { attachment -> - "${attachment.filename}: ${attachment.url}" - }) + append( + messageWithAttachments?.attachments.orEmpty() + .joinToString(separator = "\n") { attachment -> + "${attachment.filename}: ${attachment.url}" + }) } } @@ -148,7 +142,7 @@ class MessagePreviewPresenter @Inject constructor( @SuppressLint("NewApi") fun onPrint(): Boolean { - val message = message ?: return false + val message = messageWithAttachments?.message ?: return false val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() } val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss") @@ -159,8 +153,7 @@ class MessagePreviewPresenter @Inject constructor( append("

") append("

DO

${message.recipients}
") } - val messageContent = "

${message.content}

" - .replace(Regex("[\\n\\r]{2,}"), "

") + val messageContent = "

${message.content}

".replace(Regex("[\\n\\r]{2,}"), "

") .replace(Regex("[\\n\\r]"), "
") val jobName = buildString { @@ -171,9 +164,7 @@ class MessagePreviewPresenter @Inject constructor( } view?.apply { - val html = printHTML - .replace("%SUBJECT%", subject) - .replace("%CONTENT%", messageContent) + val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent) .replace("%INFO%", infoContent) printDocument(html, jobName) } @@ -182,7 +173,7 @@ class MessagePreviewPresenter @Inject constructor( } private fun deleteMessage() { - message ?: return + messageWithAttachments?.message ?: return view?.run { showContent(false) @@ -191,24 +182,22 @@ class MessagePreviewPresenter @Inject constructor( showErrorView(false) } - Timber.i("Delete message ${message?.messageGlobalKey}") + Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}") presenterScope.launch { runCatching { val student = studentRepository.getCurrentStudent(decryptPass = true) val mailbox = messageRepository.getMailboxByStudent(student) - messageRepository.deleteMessage(student, mailbox, message!!) + messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!) + }.onFailure { + retryCallback = { onMessageDelete() } + errorHandler.dispatch(it) + }.onSuccess { + view?.run { + showMessage(deleteMessageSuccessString) + popView() + } } - .onFailure { - retryCallback = { onMessageDelete() } - errorHandler.dispatch(it) - } - .onSuccess { - view?.run { - showMessage(deleteMessageSuccessString) - popView() - } - } view?.showProgress(false) } @@ -232,10 +221,10 @@ class MessagePreviewPresenter @Inject constructor( private fun initOptions() { view?.apply { showOptions( - show = message != null, - isReplayable = message?.folderId != MessageFolder.SENT.id, + show = messageWithAttachments?.message != null, + isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id, ) - message?.let { + messageWithAttachments?.message?.let { when (it.folderId == MessageFolder.TRASHED.id) { true -> setDeletedOptionsLabels() false -> setNotDeletedOptionsLabels() @@ -248,4 +237,29 @@ class MessagePreviewPresenter @Inject constructor( fun onCreateOptionsMenu() { initOptions() } + + fun onMute(): Boolean { + val message = messageWithAttachments?.message ?: return false + val isMuted = messageWithAttachments?.mutedMessageSender != null + + presenterScope.launch { + runCatching { + when (isMuted) { + true -> { + messageRepository.unmuteMessage(message.correspondents) + view?.run { showMessage(unmuteMessageSuccessString) } + } + + false -> { + messageRepository.muteMessage(message.correspondents) + view?.run { showMessage(muteMessageSuccessString) } + } + } + }.onFailure { + errorHandler.dispatch(it) + } + } + view?.updateMuteToggleButton(isMuted) + return true + } } 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 7f5f140b2..cbe1c3cbc 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 @@ -9,6 +9,10 @@ interface MessagePreviewView : BaseView { val deleteMessageSuccessString: String + val muteMessageSuccessString: String + + val unmuteMessageSuccessString: String + val messageNoSubjectString: String val printHTML: String @@ -19,6 +23,8 @@ interface MessagePreviewView : BaseView { fun setMessageWithAttachment(item: MessageWithAttachment) + fun updateMuteToggleButton(isMuted: Boolean) + fun showProgress(show: Boolean) fun showContent(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 9792c7085..fadc77e6d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -18,8 +18,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject -class MessageTabAdapter @Inject constructor() : - RecyclerView.Adapter() { +class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter() { lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit @@ -52,10 +51,11 @@ class MessageTabAdapter @Inject constructor() : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (MessageItemViewType.values()[viewType]) { + return when (MessageItemViewType.entries[viewType]) { MessageItemViewType.FILTERS -> HeaderViewHolder( ItemMessageChipsBinding.inflate(inflater, parent, false) ) + MessageItemViewType.MESSAGE -> ItemViewHolder( ItemMessageBinding.inflate(inflater, parent, false) ) @@ -137,7 +137,12 @@ class MessageTabAdapter @Inject constructor() : ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor)) isVisible = message.hasAttachments } - messageItemUnreadIndicator.isVisible = message.unread + messageItemUnreadIndicator.isVisible = message.unread || item.isMuted + + when (item.isMuted) { + true -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_notifications_off) + else -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_circle_notification) + } root.setOnClickListener { holder.bindingAdapterPosition.let { @@ -165,8 +170,7 @@ class MessageTabAdapter @Inject constructor() : RecyclerView.ViewHolder(binding.root) private class MessageTabDiffUtil( - private val old: List, - private val new: List + private val old: List, private val new: List ) : DiffUtil.Callback() { override fun getOldListSize(): Int = old.size diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt index c0bd4170e..ef640e040 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabDataItem.kt @@ -6,6 +6,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) { data class MessageItem( val message: Message, + val isMuted: Boolean, val isSelected: Boolean, val isActionMode: Boolean ) : MessageTabDataItem(MessageItemViewType.MESSAGE) 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 90f93b145..f82837214 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 @@ -4,6 +4,7 @@ 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 +import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.StudentRepository @@ -39,7 +40,7 @@ class MessageTabPresenter @Inject constructor( private var mailboxes: List = emptyList() private var selectedMailbox: Mailbox? = null - private var messages = emptyList() + private var messages = emptyList() private val searchChannel = Channel() @@ -141,7 +142,7 @@ class MessageTabPresenter @Inject constructor( } fun onActionModeSelectCheckAll() { - val messagesToSelect = getFilteredData() + val messagesToSelect = getFilteredData().map { it.message } val isAllSelected = messagesToDelete.containsAll(messagesToSelect) if (isAllSelected) { @@ -188,7 +189,7 @@ class MessageTabPresenter @Inject constructor( view?.showActionMode(false) } - val filteredData = getFilteredData() + val filteredData = getFilteredData().map { it.message } view?.run { updateActionModeTitle(messagesToDelete.size) @@ -320,25 +321,31 @@ class MessageTabPresenter @Inject constructor( } } - private fun getFilteredData(): List { + private fun getFilteredData(): List { if (lastSearchQuery.trim().isEmpty()) { - val sortedMessages = messages.sortedByDescending { it.date } + val sortedMessages = messages.sortedByDescending { it.message.date } return when { - (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } - (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread } - onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments } + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { + it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments + } + + (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread } + onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments } else -> sortedMessages } } else { val sortedMessages = messages - .map { it to calculateMatchRatio(it, lastSearchQuery) } - .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.date }) + .map { it to calculateMatchRatio(it.message, lastSearchQuery) } + .sortedWith(compareBy> { -it.second }.thenByDescending { it.first.message.date }) .filter { it.second > 6000 } .map { it.first } return when { - (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments } - (onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread } - onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments } + (onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { + it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments + } + + (onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread } + onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments } else -> sortedMessages } } @@ -367,8 +374,9 @@ class MessageTabPresenter @Inject constructor( addAll(data.map { message -> MessageTabDataItem.MessageItem( - message = message, - isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey }, + message = message.message, + isMuted = message.mutedMessageSender != null, + isSelected = messagesToDelete.any { it.messageGlobalKey == message.message.messageGlobalKey }, isActionMode = isActionMode ) }) diff --git a/app/src/main/res/drawable/ic_circle_notification.xml b/app/src/main/res/drawable/ic_circle_notification.xml new file mode 100644 index 000000000..6059212cb --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_notification.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_notifications_off.xml b/app/src/main/res/drawable/ic_notifications_off.xml new file mode 100644 index 000000000..094ed75fa --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_off.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 39fbaad01..1346c3f05 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -81,9 +81,9 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index faed4d186..5bb06a419 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -864,4 +864,10 @@ Feature disabled by your school Feature not available. Login in a mode other than Mobile API This field is required + + + Mute + Unmute + You have muted this user + You have unmuted this user diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 3a18ee979..58937e776 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -6,8 +6,10 @@ 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.dao.MutedMessageSendersDao import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.data.db.entities.MutedMessageSender import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.toFirstResult @@ -19,9 +21,16 @@ import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.Status import io.github.wulkanowy.utils.status -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.checkEquals +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList @@ -45,6 +54,9 @@ class MessageRepositoryTest { @MockK private lateinit var messageDb: MessagesDao + @MockK + private lateinit var mutesDb: MutedMessageSendersDao + @MockK private lateinit var messageAttachmentDao: MessageAttachmentDao @@ -73,9 +85,22 @@ class MessageRepositoryTest { fun setUp() { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - + coEvery { mutesDb.checkMute(any()) } returns false + coEvery { + messageDb.loadMessagesWithMutedAuthor( + mailboxKey = any(), + folder = any() + ) + } returns flowOf(emptyList()) + coEvery { + messageDb.loadMessagesWithMutedAuthor( + folder = any(), + email = any() + ) + } returns flowOf(emptyList()) repository = MessageRepository( messagesDb = messageDb, + mutedMessageSendersDao = mutesDb, messageAttachmentDao = messageAttachmentDao, sdk = sdk, context = context, @@ -131,7 +156,11 @@ class MessageRepositoryTest { @Test fun `get message when content already in db`() { val testMessage = getMessageEntity(123, "Test", false) - val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) + val messageWithAttachment = MessageWithAttachment( + testMessage, + emptyList(), + MutedMessageSender("Jan Kowalski - P - (WULKANOWY)") + ) coEvery { messageDb.loadMessageWithAttachment("v4") } returns flowOf( messageWithAttachment @@ -149,8 +178,16 @@ class MessageRepositoryTest { val testMessage = getMessageEntity(123, "", true) val testMessageWithContent = testMessage.copy().apply { content = "Test" } - val mWa = MessageWithAttachment(testMessage, emptyList()) - val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) + val mWa = MessageWithAttachment( + testMessage, + emptyList(), + MutedMessageSender("Jan Kowalski - P - (WULKANOWY)") + ) + val mWaWithContent = MessageWithAttachment( + testMessageWithContent, + emptyList(), + MutedMessageSender("Jan Kowalski - P - (WULKANOWY)") + ) coEvery { messageDb.loadMessageWithAttachment("v4") From 2c1337bb518893397e04b3ae99384e20c564e6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 29 Feb 2024 21:36:51 +0100 Subject: [PATCH 408/545] New Crowdin updates (#2439) --- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-de/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 + 6 files changed, 6 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e1cafa6ea..2e0104b10 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -854,6 +854,7 @@ Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení + This account is inactive. Try logging in again Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později Načítání dat se nezdařilo. Prosím zkuste to znovu později Je vyžadována změna hesla pro deník diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5bd71bb29..b04558aa2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -760,6 +760,7 @@ Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr + This account is inactive. Try logging in again Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal Passwortänderung für Registrierung erforderlich diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 70d4982b9..9a7ee3f81 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -854,6 +854,7 @@ Brak połączenia z internetem Wystąpił błąd. Sprawdź poprawność daty w urządzeniu + Konto jest nieaktywne. Spróbuj zalogować się ponownie Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później Ładowanie danych nie powiodło się. Spróbuj ponownie później Wymagana zmiana hasła do dziennika diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 717e02131..b7786546d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -854,6 +854,7 @@ Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве + This account is inactive. Try logging in again Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже Не удалось загрузить данные, повторите попытку позже Необходимо изменить пароль дневника diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 368ead9d5..d34302ec3 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -854,6 +854,7 @@ Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia + This account is inactive. Try logging in again Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr Načítanie údajov zlyhalo. Skúste neskôr prosím Je vyžadovaná zmena hesla pre denník diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 3d10f1179..228b87d44 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -854,6 +854,7 @@ Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою + This account is inactive. Try logging in again Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше Помилка завантаження даних, спробуйте пізніше Необхідна зміна пароля щоденника From c198e6a2f7e55910f96142524c1ae03139d1e368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 1 Mar 2024 00:06:54 +0100 Subject: [PATCH 409/545] New Crowdin updates (#2445) --- app/src/main/res/values-cs/strings.xml | 5 +++++ app/src/main/res/values-de/strings.xml | 5 +++++ app/src/main/res/values-pl/strings.xml | 5 +++++ app/src/main/res/values-ru/strings.xml | 5 +++++ app/src/main/res/values-sk/strings.xml | 5 +++++ app/src/main/res/values-uk/strings.xml | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2e0104b10..5c4c52da8 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -866,4 +866,9 @@ Funkce je deaktivována přes vaší školou Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API Toto pole je povinné + + Mute + Unmute + You have muted this user + You have unmuted this user diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b04558aa2..a346bbd2f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -772,4 +772,9 @@ Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar Dieses Feld ist erforderlich + + Mute + Unmute + You have muted this user + You have unmuted this user diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9a7ee3f81..56a85ea2a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -866,4 +866,9 @@ Funkcja wyłączona przez szkołę Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API To pole jest wymagane + + Wycisz + Wyłącz wyciszenie + Wyciszyleś tego użytkownika + Wyłączyłeś wyciszenie tego użytkownika diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b7786546d..f7469675e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -866,4 +866,9 @@ Функция отключена вашей школой Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом Это поле обязательно + + Mute + Unmute + You have muted this user + You have unmuted this user diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d34302ec3..56238c10a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -866,4 +866,9 @@ Funkcia je deaktivovaná cez vašou školou Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API Toto pole je povinné + + Mute + Unmute + You have muted this user + You have unmuted this user diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 228b87d44..a82027479 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -866,4 +866,9 @@ Функція вимкнена вашою школою Функція недоступна в режимі Mobile API. Увійдіть в інший режим Це поле обовʼязкове + + Mute + Unmute + You have muted this user + You have unmuted this user From c04752ed39e847009bc4ab1997a4aef43a545ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 1 Mar 2024 10:32:55 +0100 Subject: [PATCH 410/545] Fix timetable items layout (#2446) --- app/src/main/res/layout/item_timetable.xml | 11 +++++++---- .../main/res/layout/subitem_dashboard_small_grade.xml | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/item_timetable.xml b/app/src/main/res/layout/item_timetable.xml index b9966c121..d13105229 100644 --- a/app/src/main/res/layout/item_timetable.xml +++ b/app/src/main/res/layout/item_timetable.xml @@ -24,6 +24,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0" tools:text="5" /> @@ -179,7 +182,7 @@ android:visibility="gone" app:backgroundTint="?colorPrimary" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toTopOf="@id/timetableItemTimeStart" tools:text="jeszcze 15 min" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml index 3684c2677..5d48313a3 100644 --- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml +++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml @@ -11,6 +11,7 @@ android:gravity="center" android:maxLength="5" android:minWidth="20dp" + android:padding="1dp" android:textColor="@android:color/white" android:textSize="12sp" android:textStyle="bold" From ea28fc783cf2c24e25606f5ba13dff120d1101ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 1 Mar 2024 21:14:43 +0100 Subject: [PATCH 411/545] Add message from trash restoring (#2438) --- .../wulkanowy/data/enums/MessageFolder.kt | 7 ++- .../data/repositories/MessageRepository.kt | 50 ++++++++++----- .../message/preview/MessagePreviewFragment.kt | 27 ++++---- .../preview/MessagePreviewPresenter.kt | 61 +++++++++++++++---- .../message/preview/MessagePreviewView.kt | 8 +-- .../message/send/SendMessagePresenter.kt | 2 +- .../modules/message/tab/MessageTabFragment.kt | 16 +++-- .../message/tab/MessageTabPresenter.kt | 23 ++++++- .../ic_menu_message_delete_forever.xml | 9 +++ .../res/drawable/ic_menu_message_restore.xml | 9 +++ .../res/menu/action_menu_message_preview.xml | 14 +++++ .../res/menu/context_menu_message_tab.xml | 14 +++++ app/src/main/res/values/strings.xml | 3 + 13 files changed, 192 insertions(+), 51 deletions(-) create mode 100644 app/src/main/res/drawable/ic_menu_message_delete_forever.xml create mode 100644 app/src/main/res/drawable/ic_menu_message_restore.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt index 899ba9085..7cb4202a1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt @@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums enum class MessageFolder(val id: Int = 1) { RECEIVED(1), SENT(2), - TRASHED(3) + TRASHED(3), + ; + + companion object { + fun byId(id: Int) = entries.first { it.id == id } + } } 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 6d591c5bb..96f048706 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 @@ -18,6 +18,7 @@ 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.SENT import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapToEntities @@ -25,6 +26,7 @@ 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.toFirstResult import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.sdk.Sdk @@ -34,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -155,17 +156,30 @@ class MessageRepository @Inject constructor( subject: String, content: String, recipients: List, - mailboxId: String, + mailbox: Mailbox, ) { sdk.init(student).sendMessage( subject = subject, content = content, recipients = recipients.mapFromEntities(), - mailboxId = mailboxId, + mailboxId = mailbox.globalKey, ) + refreshFolders(student, mailbox, listOf(SENT)) } - suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List) { + suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) { + sdk.init(student).restoreMessages( + messages = messages.map { it.messageGlobalKey }, + ) + + refreshFolders(student, mailbox) + } + + suspend fun deleteMessage(student: Student, message: Message) { + deleteMessages(student, listOf(message)) + } + + suspend fun deleteMessages(student: Student, messages: List) { val firstMessage = messages.first() sdk.init(student).deleteMessages( messages = messages.map { it.messageGlobalKey }, @@ -184,18 +198,24 @@ class MessageRepository @Inject constructor( } messagesDb.updateAll(deletedMessages) - } else messagesDb.deleteAll(messages) - - getMessages( - student = student, - mailbox = mailbox, - folder = TRASHED, - forceRefresh = true, - ).first() + } else { + messagesDb.deleteAll(messages) + } } - suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) { - deleteMessages(student, mailbox, listOf(message)) + private suspend fun refreshFolders( + student: Student, + mailbox: Mailbox?, + folders: List = MessageFolder.entries + ) { + folders.forEach { + getMessages( + student = student, + mailbox = mailbox, + folder = it, + forceRefresh = true, + ).toFirstResult() + } } suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource( @@ -240,7 +260,7 @@ class MessageRepository @Inject constructor( value?.let { json.encodeToString(it) } ) - suspend fun isMuted(author: String): Boolean { + private suspend fun isMuted(author: String): Boolean { return mutedMessageSendersDao.checkMute(author) } 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 3b33bb51f..75778bac5 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 @@ -44,8 +44,12 @@ class MessagePreviewFragment : private var menuForwardButton: MenuItem? = null + private var menuRestoreButton: MenuItem? = null + private var menuDeleteButton: MenuItem? = null + private var menuDeleteForeverButton: MenuItem? = null + private var menuShareButton: MenuItem? = null private var menuPrintButton: MenuItem? = null @@ -64,6 +68,9 @@ class MessagePreviewFragment : override val unmuteMessageSuccessString: String get() = getString(R.string.message_unmute_success) + override val restoreMessageSuccessString: String + get() = getString(R.string.message_restore_success) + override val messageNoSubjectString: String get() = getString(R.string.message_no_subject) @@ -111,7 +118,9 @@ class MessagePreviewFragment : inflater.inflate(R.menu.action_menu_message_preview, menu) menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward) + menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore) menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) + menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever) menuShareButton = menu.findItem(R.id.messagePreviewMenuShare) menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint) menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute) @@ -124,7 +133,9 @@ class MessagePreviewFragment : return when (item.itemId) { R.id.messagePreviewMenuReply -> presenter.onReply() R.id.messagePreviewMenuForward -> presenter.onForward() + R.id.messagePreviewMenuRestore -> presenter.onMessageRestore() R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() + R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete() R.id.messagePreviewMenuShare -> presenter.onShare() R.id.messagePreviewMenuPrint -> presenter.onPrint() R.id.messagePreviewMenuMute -> presenter.onMute() @@ -152,23 +163,17 @@ class MessagePreviewFragment : binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE } - override fun showOptions(show: Boolean, isReplayable: Boolean) { - menuReplyButton?.isVisible = isReplayable + override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) { + menuReplyButton?.isVisible = show && isReplayable menuForwardButton?.isVisible = show - menuDeleteButton?.isVisible = show + menuRestoreButton?.isVisible = show && isRestorable + menuDeleteButton?.isVisible = show && !isRestorable + menuDeleteForeverButton?.isVisible = show && isRestorable menuShareButton?.isVisible = show menuPrintButton?.isVisible = show menuMuteButton?.isVisible = show && isReplayable } - override fun setDeletedOptionsLabels() { - menuDeleteButton?.setTitle(R.string.message_delete_forever) - } - - override fun setNotDeletedOptionsLabels() { - menuDeleteButton?.setTitle(R.string.message_move_to_trash) - } - override fun showErrorView(show: Boolean) { binding.messagePreviewError.visibility = if (show) VISIBLE else GONE } 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 2eff245ff..9bb0d32a4 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 @@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds class MessagePreviewPresenter @Inject constructor( errorHandler: ErrorHandler, @@ -74,6 +76,7 @@ class MessagePreviewPresenter @Inject constructor( } } } else { + delay(1.seconds) view?.run { showMessage(messageNotExists) popView() @@ -172,13 +175,51 @@ class MessagePreviewPresenter @Inject constructor( return true } + private fun restoreMessage() { + val message = messageWithAttachments?.message ?: return + + view?.run { + showContent(false) + showProgress(true) + showOptions( + show = false, + isReplayable = false, + isRestorable = false, + ) + showErrorView(false) + } + Timber.i("Restore message ${message.messageGlobalKey}") + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(decryptPass = true) + val mailbox = messageRepository.getMailboxByStudent(student) + messageRepository.restoreMessages(student, mailbox, listOfNotNull(message)) + } + .onFailure { + retryCallback = { onMessageRestore() } + errorHandler.dispatch(it) + } + .onSuccess { + view?.run { + showMessage(restoreMessageSuccessString) + popView() + } + } + view?.showProgress(false) + } + } + private fun deleteMessage() { messageWithAttachments?.message ?: return view?.run { showContent(false) showProgress(true) - showOptions(show = false, isReplayable = false) + showOptions( + show = false, + isReplayable = false, + isRestorable = false, + ) showErrorView(false) } @@ -187,8 +228,7 @@ class MessagePreviewPresenter @Inject constructor( presenterScope.launch { runCatching { val student = studentRepository.getCurrentStudent(decryptPass = true) - val mailbox = messageRepository.getMailboxByStudent(student) - messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!) + messageRepository.deleteMessage(student, messageWithAttachments?.message!!) }.onFailure { retryCallback = { onMessageDelete() } errorHandler.dispatch(it) @@ -213,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor( } } + fun onMessageRestore(): Boolean { + restoreMessage() + return true + } + fun onMessageDelete(): Boolean { deleteMessage() return true @@ -222,15 +267,9 @@ class MessagePreviewPresenter @Inject constructor( view?.apply { showOptions( show = messageWithAttachments?.message != null, - isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id, + isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id, + isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id, ) - messageWithAttachments?.message?.let { - when (it.folderId == MessageFolder.TRASHED.id) { - true -> setDeletedOptionsLabels() - false -> setNotDeletedOptionsLabels() - } - } - } } 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 cbe1c3cbc..ee0b6ce0a 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 @@ -13,6 +13,8 @@ interface MessagePreviewView : BaseView { val unmuteMessageSuccessString: String + val restoreMessageSuccessString: String + val messageNoSubjectString: String val printHTML: String @@ -35,11 +37,7 @@ interface MessagePreviewView : BaseView { fun setErrorRetryCallback(callback: () -> Unit) - fun showOptions(show: Boolean, isReplayable: Boolean) - - fun setDeletedOptionsLabels() - - fun setNotDeletedOptionsLabels() + fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) fun openMessageReply(message: Message?) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index e776e9941..6155baea3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor( subject = subject, content = content, recipients = recipients, - mailboxId = mailbox.globalKey, + mailbox = mailbox, ) }.logResourceStatus("sending message").onEach { when (it) { 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 4364e8681..12f9d3234 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 @@ -5,7 +5,9 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.View.* +import android.view.View.GONE +import android.view.View.INVISIBLE +import android.view.View.VISIBLE import android.widget.CompoundButton import androidx.annotation.StringRes import androidx.appcompat.view.ActionMode @@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment(R.layout.frag } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - if (presenter.folder == MessageFolder.TRASHED) { - val menuItem = menu.findItem(R.id.messageTabContextMenuDelete) - menuItem.setTitle(R.string.message_delete_forever) - } + val isTrashFolder = presenter.folder == MessageFolder.TRASHED + + menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder) + menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder) + menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder) + return presenter.onPrepareActionMode() } @@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment(R.layout.frag override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean { when (menu.itemId) { R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete() + R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore() + R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete() R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll() } return true 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 f82837214..cda0b32bd 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 @@ -121,8 +121,27 @@ class MessageTabPresenter @Inject constructor( return true } + fun onActionModeSelectRestore() { + Timber.i("Restore ${messagesToDelete.size} messages") + val messageList = messagesToDelete.toList() + + presenterScope.launch { + view?.run { + showProgress(true) + showContent(false) + showActionMode(false) + } + runCatching { + val student = studentRepository.getCurrentStudent(true) + messageRepository.restoreMessages(student, selectedMailbox, messageList) + } + .onFailure(errorHandler::dispatch) + .onSuccess { view?.showMessage(R.string.message_messages_restored) } + } + } + fun onActionModeSelectDelete() { - Timber.i("Delete ${messagesToDelete.size} messages)") + Timber.i("Delete ${messagesToDelete.size} messages") val messageList = messagesToDelete.toList() presenterScope.launch { @@ -134,7 +153,7 @@ class MessageTabPresenter @Inject constructor( runCatching { val student = studentRepository.getCurrentStudent(true) - messageRepository.deleteMessages(student, selectedMailbox, messageList) + messageRepository.deleteMessages(student, messageList) } .onFailure(errorHandler::dispatch) .onSuccess { view?.showMessage(R.string.message_messages_deleted) } diff --git a/app/src/main/res/drawable/ic_menu_message_delete_forever.xml b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml new file mode 100644 index 000000000..a7b5ac53b --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_delete_forever.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_message_restore.xml b/app/src/main/res/drawable/ic_menu_message_restore.xml new file mode 100644 index 000000000..5c8544f28 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_message_restore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/action_menu_message_preview.xml b/app/src/main/res/menu/action_menu_message_preview.xml index 224788674..04af86713 100644 --- a/app/src/main/res/menu/action_menu_message_preview.xml +++ b/app/src/main/res/menu/action_menu_message_preview.xml @@ -29,6 +29,13 @@ android:title="@string/message_forward" app:iconTint="@color/material_on_surface_emphasis_medium" app:showAsAction="ifRoom" /> + +

+ + Forward Select all Unselect all + Restore from trash Move to trash Delete permanently + Message restored successfully Message deleted successfully student parent @@ -364,6 +366,7 @@ %1$d selected Messages deleted + Messages restored Choose mailbox Incognito mode is on Thanks to incognito mode sender is not notified when you read the message From a7238e3f23703bdcc0af20b06fc09e904fee4ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 1 Mar 2024 22:16:56 +0100 Subject: [PATCH 412/545] New Crowdin updates (#2447) --- app/src/main/res/values-cs/strings.xml | 3 +++ app/src/main/res/values-de/strings.xml | 3 +++ app/src/main/res/values-pl/strings.xml | 3 +++ app/src/main/res/values-ru/strings.xml | 3 +++ app/src/main/res/values-sk/strings.xml | 3 +++ app/src/main/res/values-uk/strings.xml | 3 +++ 6 files changed, 18 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 5c4c52da8..fbc92e46f 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -336,8 +336,10 @@ Poslat dále Vybrat vše Odznačit vše + Restore from trash Přesunout do koše Odstranit natrvalo + Message restored successfully Zpráva byla úspěšně odstraněna žák rodič @@ -383,6 +385,7 @@ %1$d vybraných Zprávy odstraněné + Messages restored 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 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a346bbd2f..1f1246004 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -296,8 +296,10 @@ Weiterleiten Alle auswählen Alle abwählen + Restore from trash In Papierkorb verschieben Dauerhaft löschen + Message restored successfully Nachricht erfolgreich gelöscht schüler Eltern @@ -335,6 +337,7 @@ %1$d ausgewählt Nachrichten gelöscht + Messages restored Postfach auswählen Incognito mode is on Thanks to incognito mode sender is not notified when you read the message diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 56a85ea2a..597d843df 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -336,8 +336,10 @@ Prześlij dalej Zaznacz wszystkie Odznacz wszystkie + Przywróć z kosza Przenieś do kosza Usuń trwale + Wiadomość przywrócona pomyślnie Wiadomość usunięta pomyślnie uczeń rodzic @@ -383,6 +385,7 @@ %1$d wybranych Wiadomości zostały usunięte + Wiadomości przywrócone Wybierz skrzynkę Tryb incognito jest włączony Dzięki trybowi incognito nadawca nie zobaczy, że przeczytałeś tę wiadomość diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f7469675e..46a19c71f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -336,8 +336,10 @@ Переслать Выбрать все Отменить выбор + Restore from trash Перенести в корзину Удалить навсегда + Message restored successfully Сообщение успешно удалено ученик родитель @@ -383,6 +385,7 @@ %1$d выбрано Сообщение удалено + Messages restored Выбрать почтовый ящик Incognito mode is on Thanks to incognito mode sender is not notified when you read the message diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 56238c10a..b63c07c6f 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -336,8 +336,10 @@ Poslať ďalej Vybrať všetko Odznačiť všetko + Restore from trash Presunúť do koša Odstrániť natrvalo + Message restored successfully Správa bola úspešne odstránená žiak rodič @@ -383,6 +385,7 @@ %1$d vybraných Správy odstránené + Messages restored 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 diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a82027479..8116c7e48 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -336,8 +336,10 @@ Переслати Вибрати всі Відмінити вибір + Restore from trash Перемістити до кошика Видалити назавжди + Message restored successfully Лист було успішно видалено учень родич @@ -383,6 +385,7 @@ %1$d вибрано Листи видалено + Messages restored Вибрати поштову скриньку Режим анонімності включено Завдяки режиму анонімності, відправник не буде сповіщений коли ви прочитаєте повідомлення From ccba31f2e81d2aa36e3ff64912ad57a5c7a92102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 16:55:54 +0100 Subject: [PATCH 413/545] Add last announcements to school announcements (#2452) --- .../61.json | 2533 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../data/db/entities/SchoolAnnouncement.kt | 4 +- .../data/mappers/DirectorInformationMapper.kt | 14 + .../SchoolAnnouncementRepository.kt | 7 +- .../SchoolAnnouncementAdapter.kt | 5 + .../res/layout/item_school_announcement.xml | 33 +- 7 files changed, 2585 insertions(+), 14 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json new file mode 100644 index 000000000..e36dcc8a6 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json @@ -0,0 +1,2533 @@ +{ + "formatVersion": 1, + "database": { + "version": 61, + "identityHash": "41fbd2ff00aba10b2ef0a079e6037c87", + "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, `author` TEXT, `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": "author", + "columnName": "author", + "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": "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, '41fbd2ff00aba10b2ef0a079e6037c87')" + ] + } +} \ 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 21a6e3f3e..208daf75f 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 @@ -173,6 +173,7 @@ import javax.inject.Singleton AutoMigration(from = 57, to = 58, spec = Migration58::class), AutoMigration(from = 58, to = 59), AutoMigration(from = 59, to = 60), + AutoMigration(from = 60, to = 61), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -181,7 +182,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 60 + const val VERSION_SCHEMA = 61 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt index 25e27ef18..ac096b02b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt @@ -16,7 +16,9 @@ data class SchoolAnnouncement( val subject: String, - val content: String + val content: String, + + val author: String? = null, ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt index 16f1bbac0..85b37afc1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt @@ -3,12 +3,26 @@ package io.github.wulkanowy.data.mappers import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation +import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement +@JvmName("mapDirectorInformationToEntities") fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( userLoginId = student.userLoginId, date = it.date, subject = it.subject, content = it.content, + author = null, + ) +} + +@JvmName("mapLastAnnouncementsToEntities") +fun List.mapToEntities(student: Student) = map { + SchoolAnnouncement( + userLoginId = student.userLoginId, + date = it.date, + subject = it.subject, + content = it.content, + author = it.author, ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index 4c42d092f..8537fbc3e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -41,9 +41,10 @@ class SchoolAnnouncementRepository @Inject constructor( schoolAnnouncementDb.loadAll(student.userLoginId) }, fetch = { - sdk.init(student) - .getDirectorInformation() - .mapToEntities(student) + val sdk = sdk.init(student) + val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student) + val directorInformation = sdk.getDirectorInformation().mapToEntities(student) + lastAnnouncements + directorInformation }, saveFetchResult = { old, new -> val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt index 46999599b..731488a9c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementAdapter.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.schoolannouncement import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding @@ -29,6 +30,10 @@ class SchoolAnnouncementAdapter @Inject constructor() : schoolAnnouncementItemDate.text = item.date.toFormattedString() schoolAnnouncementItemType.text = item.subject schoolAnnouncementItemContent.text = item.content.parseUonetHtml() + with(schoolAnnouncementItemAuthor) { + text = item.author + isVisible = !item.author.isNullOrBlank() + } root.setOnClickListener { onItemClickListener(item) } } diff --git a/app/src/main/res/layout/item_school_announcement.xml b/app/src/main/res/layout/item_school_announcement.xml index bb0cffd16..1197c66c7 100644 --- a/app/src/main/res/layout/item_school_announcement.xml +++ b/app/src/main/res/layout/item_school_announcement.xml @@ -11,27 +11,41 @@ android:id="@+id/schoolAnnouncementItemDate" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="15dp" + android:layout_marginHorizontal="15dp" android:layout_marginTop="10dp" - android:layout_marginEnd="10dp" android:textColor="?android:textColorSecondary" android:textSize="15sp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/schoolAnnouncementItemAuthor" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/date/ddmmyy" /> + + @@ -40,6 +54,7 @@ android:id="@+id/schoolAnnouncementItemContent" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginHorizontal="15dp" android:layout_marginTop="5dp" android:layout_marginBottom="15dp" android:ellipsize="end" @@ -47,8 +62,8 @@ android:maxLines="2" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/schoolAnnouncementItemType" - app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemType" tools:text="@tools:sample/lorem/random" /> From f2d26453ed330e49590194ceda5bc3c5f3b8e822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 17:01:12 +0100 Subject: [PATCH 414/545] Fix calculating average with optional arithmetic average on and no grade with average in second semester (#2448) --- .../ui/modules/grade/GradeAverageProvider.kt | 27 ++++++--- .../modules/grade/GradeAverageProviderTest.kt | 56 +++++++++++++++++-- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index e8a5fa254..8da59eaf4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -159,7 +159,7 @@ class GradeAverageProvider @Inject constructor( ?.updateModifiers(student, config).orEmpty() (updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage( - config.isOptionalArithmeticAverage + isOptionalArithmeticAverage = config.isOptionalArithmeticAverage, ) } else { secondSemesterSubject.average @@ -173,13 +173,21 @@ class GradeAverageProvider @Inject constructor( config: AverageCalcParams, ): Double { return if (!isAnyVulcanAverage || config.forceAverageCalc) { - val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 + val isSecondSemesterHasWeightGrade = secondSemesterSubject.grades + .any { it.weightValue > .0 } + val isSecondSemesterHasArithmeticGrade = secondSemesterSubject.grades + .all { it.weightValue == .0 } && config.isOptionalArithmeticAverage + val isSecondSemesterHaveAverage = + isSecondSemesterHasWeightGrade || isSecondSemesterHasArithmeticGrade + + val divider = if (isSecondSemesterHaveAverage) 2 else 1 val secondSemesterAverage = secondSemesterSubject.grades .updateModifiers(student, config) - .calcAverage(config.isOptionalArithmeticAverage) + .calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage) val firstSemesterAverage = firstSemesterSubject?.grades ?.updateModifiers(student, config) - ?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage + ?.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage) + ?: secondSemesterAverage (secondSemesterAverage + firstSemesterAverage) / divider } else { @@ -225,7 +233,7 @@ class GradeAverageProvider @Inject constructor( subject = summary.subject, average = if (!isAnyAverage || params.forceAverageCalc) { grades.updateModifiers(student, params) - .calcAverage(params.isOptionalArithmeticAverage) + .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage) } else summary.average, points = summary.pointsSum, summary = summary, @@ -286,8 +294,13 @@ class GradeAverageProvider @Inject constructor( proposedPoints = "", finalPoints = "", pointsSum = "", - average = if (calcAverage) details.updateModifiers(student, params) - .calcAverage(params.isOptionalArithmeticAverage) else .0 + average = when { + calcAverage -> details + .updateModifiers(student, params) + .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage) + + else -> .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 6a717f6f6..4f0f80fe1 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 @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -112,8 +113,8 @@ class GradeAverageProviderTest { private val secondGradeWithModifier = listOf( // avg: 3.375 - getGrade(24, "Język polski", 3.0, -0.50), - getGrade(24, "Język polski", 4.0, 0.25) + getGrade(24, "Język polski", 3.0, -0.50, entry = "3-"), + getGrade(24, "Język polski", 4.0, 0.25, entry = "4+") ) private val secondSummariesWithModifier = listOf( @@ -122,8 +123,8 @@ class GradeAverageProviderTest { private val noWeightGrades = listOf( // standard: 0.0, arithmetic: 4.0 - getGrade(22, "Matematyka", 5.0, 0.0, 0.0), - getGrade(22, "Matematyka", 3.0, 0.0, 0.0), + getGrade(22, "Matematyka", 5.0, 0.0, 0.0, "5"), + getGrade(22, "Matematyka", 3.0, 0.0, 0.0, "3"), getGrade(22, "Matematyka", 1.0, 0.0, 0.0, "np.") ) @@ -132,7 +133,7 @@ class GradeAverageProviderTest { ) private val noWeightGradesArithmeticSummary = listOf( - getSummary(23, "Matematyka", 4.0) + getSummary(23, "Matematyka", .0) ) @Before @@ -211,6 +212,51 @@ class GradeAverageProviderTest { ) // from summary: 4,0 } + @Test + fun `calc current semester arithmetic average with no weights in second semester`() = runTest { + every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(false) + every { preferencesRepository.isOptionalArithmeticAverageFlow } returns flowOf(true) + every { preferencesRepository.gradeAverageModeFlow } returns flowOf(GradeAverageMode.BOTH_SEMESTERS) + coEvery { + gradeRepository.getGrades( + student = student, + semester = semesters[1], + forceRefresh = true, + ) + } returns resourceFlow { + Triple( + first = noWeightGrades, + second = noWeightGradesArithmeticSummary, + third = emptyList(), + ) + } + coEvery { + gradeRepository.getGrades( + student = student, + semester = semesters[2], + forceRefresh = true, + ) + } returns resourceFlow { + Triple( + first = noWeightGrades, + second = noWeightGradesArithmeticSummary, + third = emptyList(), + ) + } + + val items = gradeAverageProvider.getGradesDetailsWithAverage( + student = student, + semesterId = semesters[2].semesterId, + forceRefresh = true + ).getResult() + + assertEquals( + 4.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // from summary: 4,0 + } + @Test fun `calc current semester average with load from cache sequence`() { every { preferencesRepository.gradeAverageForceCalcFlow } returns flowOf(true) From 3564366a8f04aafc5ba1a91b875ba39f62eda798 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:08:32 +0000 Subject: [PATCH 415/545] Bump com.google.firebase:firebase-bom from 32.7.2 to 32.7.3 (#2453) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 07efeb2f2..f4ead9436 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.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.7.2') + playImplementation platform('com.google.firebase:firebase-bom:32.7.3') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From 05bda598fc5e60ff1c9ea88efda13f27a5007fbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:10:19 +0000 Subject: [PATCH 416/545] Bump mockk from 1.13.9 to 1.13.10 (#2455) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f4ead9436..f01b9917c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ ext { android_hilt = "1.2.0" room = "2.6.1" chucker = "4.0.0" - mockk = "1.13.9" + mockk = "1.13.10" coroutines = "1.8.0" } From e9d64de0cbe88fec1a228a00a934d0b9b300dc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 17:10:38 +0100 Subject: [PATCH 417/545] Improve invalid password message (#2451) --- .../github/wulkanowy/ui/base/BaseActivity.kt | 25 +++++++++++++++++++ .../wulkanowy/utils/ExceptionExtension.kt | 2 ++ app/src/main/res/values/strings.xml | 5 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 29996db7c..10735dab3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -17,6 +17,8 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.openInternetBrowser +import timber.log.Timber +import java.time.Instant import javax.inject.Inject abstract class BaseActivity, VB : ViewBinding> : @@ -36,6 +38,8 @@ abstract class BaseActivity, VB : ViewBinding> : abstract var presenter: T + private var lastDialogOpenTime = mutableMapOf() + override fun onCreate(savedInstanceState: Bundle?) { inject() themeManager.applyActivityTheme(this) @@ -70,6 +74,8 @@ abstract class BaseActivity, VB : ViewBinding> : } override fun showExpiredCredentialsDialog() { + if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return + MaterialAlertDialogBuilder(this) .setTitle(R.string.main_expired_credentials_title) .setMessage(R.string.main_expired_credentials_description) @@ -83,6 +89,8 @@ abstract class BaseActivity, VB : ViewBinding> : } override fun showDecryptionFailedDialog() { + if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return + MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) .setMessage(R.string.main_session_relogin) @@ -119,4 +127,21 @@ abstract class BaseActivity, VB : ViewBinding> : protected open fun inject() { throw UnsupportedOperationException() } + + private fun shouldShowDialog(name: String): Boolean { + val lastOpenTime = lastDialogOpenTime[name] + val now = Instant.now() + + if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) { + Timber.i("Dialog $name was shown less than a second ago. Skip") + return false + } + lastDialogOpenTime[name] = Instant.now() + return true + } + + companion object { + private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials" + private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed" + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt index 1c2290510..d541c0a7e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException import io.github.wulkanowy.sdk.scrapper.exception.VulcanException +import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import okhttp3.internal.http2.StreamResetException @@ -34,6 +35,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) { is ServiceUnavailableException -> R.string.error_service_unavailable is FeatureDisabledException -> R.string.error_feature_disabled is FeatureNotAvailableException -> R.string.error_feature_not_available + is BadCredentialsException -> R.string.error_password_invalid is AccountInactiveException -> R.string.error_account_inactive is VulcanException -> R.string.error_unknown_uonet is ScrapperException -> R.string.error_unknown_app diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6843f1aa0..266c3522c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,8 +109,8 @@ Log in Session expired Session expired, log in again - Your account password has been changed. You need to log in to Wulkanowy again - Password changed + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy 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 @@ -858,6 +858,7 @@ This account is inactive. Try logging in again Connection to register failed. Servers can be overloaded. Please try again later Loading data failed. Please try again later + Your password has expired or been changed. Please log in again Register password change required Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later From dc9af29a441991610e79e25bd8de529d14b3f019 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:11:13 +0000 Subject: [PATCH 418/545] Bump hilt_version from 2.50 to 2.51 (#2456) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f7f3d209e..a23f2191b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.22' about_libraries = '10.10.0' - hilt_version = '2.50' + hilt_version = '2.51' } repositories { mavenCentral() From fb240938ed69801da29040a16245079c5989ac1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:21:55 +0000 Subject: [PATCH 419/545] Bump about_libraries from 10.10.0 to 11.1.0 (#2454) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a23f2191b..ec19ee49a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.22' - about_libraries = '10.10.0' + about_libraries = '11.1.0' hilt_version = '2.51' } repositories { From 333306e7ba57c278bafe33e0310cc7e88eb5574b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 17:25:27 +0100 Subject: [PATCH 420/545] Wrap delete and save operations in database transactions (#2450) --- .../wulkanowy/data/db/dao/AdminMessageDao.kt | 14 +-- .../github/wulkanowy/data/db/dao/BaseDao.kt | 7 ++ .../data/repositories/AttendanceRepository.kt | 7 +- .../AttendanceSummaryRepository.kt | 9 +- .../CompletedLessonsRepository.kt | 14 ++- .../data/repositories/ConferenceRepository.kt | 12 +- .../data/repositories/ExamRepository.kt | 12 +- .../data/repositories/GradeRepository.kt | 87 +++++++------ .../repositories/GradeStatisticsRepository.kt | 22 ++-- .../data/repositories/HomeworkRepository.kt | 12 +- .../repositories/LuckyNumberRepository.kt | 11 +- .../data/repositories/MessageRepository.kt | 13 +- .../repositories/MobileDeviceRepository.kt | 7 +- .../data/repositories/NoteRepository.kt | 17 ++- .../data/repositories/RecipientRepository.kt | 12 +- .../SchoolAnnouncementRepository.kt | 12 +- .../data/repositories/SchoolRepository.kt | 8 +- .../data/repositories/SemesterRepository.kt | 15 ++- .../repositories/StudentInfoRepository.kt | 10 +- .../data/repositories/SubjectRepository.kt | 7 +- .../data/repositories/TeacherRepository.kt | 7 +- .../data/repositories/TimetableRepository.kt | 18 ++- .../repositories/AttendanceRepositoryTest.kt | 87 +++++++++---- .../CompletedLessonsRepositoryTest.kt | 117 +++++++++++------- .../data/repositories/ExamRemoteTest.kt | 66 ++++++---- .../data/repositories/GradeRepositoryTest.kt | 81 +++++++----- .../GradeStatisticsRepositoryTest.kt | 12 +- .../repositories/LuckyNumberRemoteTest.kt | 51 +++++--- .../repositories/MessageRepositoryTest.kt | 17 +-- .../MobileDeviceRepositoryTest.kt | 101 +++++++++------ .../data/repositories/RecipientLocalTest.kt | 20 ++- .../repositories/SemesterRepositoryTest.kt | 51 +++++--- .../repositories/TimetableRepositoryTest.kt | 17 +-- 33 files changed, 587 insertions(+), 366 deletions(-) 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 2b4cb5975..6c8d7e471 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 @@ -2,24 +2,14 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Dao import androidx.room.Query -import androidx.room.Transaction import io.github.wulkanowy.data.db.entities.AdminMessage import kotlinx.coroutines.flow.Flow import javax.inject.Singleton @Singleton @Dao -abstract class AdminMessageDao : BaseDao { +interface AdminMessageDao : BaseDao { @Query("SELECT * FROM AdminMessages") - abstract fun loadAll(): Flow> - - @Transaction - open suspend fun removeOldAndSaveNew( - oldMessages: List, - newMessages: List - ) { - deleteAll(oldMessages) - insertAll(newMessages) - } + fun loadAll(): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt index 056a5cbd1..937e98248 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/BaseDao.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy +import androidx.room.Transaction import androidx.room.Update interface BaseDao { @@ -15,4 +16,10 @@ interface BaseDao { @Delete suspend fun deleteAll(items: List) + + @Transaction + suspend fun removeOldAndSaveNew(oldItems: List, newItems: List) { + deleteAll(oldItems) + insertAll(newItems) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index bbf627de0..46ea29f83 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -65,12 +65,13 @@ class AttendanceRepository @Inject constructor( .mapToEntities(semester, lessons) }, saveFetchResult = { old, new -> - attendanceDb.deleteAll(old uniqueSubtract new) val attendanceToAdd = (new uniqueSubtract old).map { newAttendance -> newAttendance.apply { if (notify) isNotified = false } } - attendanceDb.insertAll(attendanceToAdd) - + attendanceDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = attendanceToAdd, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) }, filterResult = { it.filter { item -> item.date in start..end } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index 6bdcf9d7f..c6cfc2f6b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -1,5 +1,7 @@ package io.github.wulkanowy.data.repositories +import androidx.room.withTransaction +import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -20,6 +22,7 @@ class AttendanceSummaryRepository @Inject constructor( private val attendanceDb: AttendanceSummaryDao, private val sdk: Sdk, private val refreshHelper: AutoRefreshHelper, + private val appDatabase: AppDatabase, ) { private val saveFetchResultMutex = Mutex() @@ -46,8 +49,10 @@ class AttendanceSummaryRepository @Inject constructor( .mapToEntities(semester, subjectId) }, saveFetchResult = { old, new -> - attendanceDb.deleteAll(old uniqueSubtract new) - attendanceDb.insertAll(new uniqueSubtract old) + appDatabase.withTransaction { + attendanceDb.deleteAll(old uniqueSubtract new) + attendanceDb.insertAll(new uniqueSubtract old) + } refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index 1579ae62b..f7f86b23d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -6,7 +6,13 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate import javax.inject.Inject @@ -53,8 +59,10 @@ class CompletedLessonsRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - completedLessonsDb.deleteAll(old uniqueSubtract new) - completedLessonsDb.insertAll(new uniqueSubtract old) + completedLessonsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) }, filterResult = { it.filter { item -> item.date in start..end } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index 7eb37f0b7..fbe578604 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -53,12 +53,12 @@ class ConferenceRepository @Inject constructor( .filter { it.date >= startDate } }, saveFetchResult = { old, new -> - val conferencesToSave = (new uniqueSubtract old).onEach { - if (notify) it.isNotified = false - } - - conferenceDb.deleteAll(old uniqueSubtract new) - conferenceDb.insertAll(conferencesToSave) + conferenceDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 96026a55b..9b8dd02e3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -62,12 +62,12 @@ class ExamRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - val examsToSave = (new uniqueSubtract old).onEach { - if (notify) it.isNotified = false - } - - examDb.deleteAll(old uniqueSubtract new) - examDb.insertAll(examsToSave) + examDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) }, filterResult = { it.filter { item -> item.date in start..end } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index 1e2ea9354..ac1ef541b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -87,10 +87,12 @@ class GradeRepository @Inject constructor( new: List, notify: Boolean ) { - gradeDescriptiveDb.deleteAll(old uniqueSubtract new) - gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach { - if (notify) it.isNotified = false - }) + gradeDescriptiveDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) } private suspend fun refreshGradeDetails( @@ -101,13 +103,16 @@ class GradeRepository @Inject constructor( ) { val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate() - gradeDb.deleteAll(oldGrades uniqueSubtract newDetails) - gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { - if (it.date >= notifyBreakDate) it.apply { - isRead = false - if (notify) isNotified = false - } - }) + + gradeDb.removeOldAndSaveNew( + oldItems = oldGrades uniqueSubtract newDetails, + newItems = (newDetails uniqueSubtract oldGrades).onEach { + if (it.date >= notifyBreakDate) it.apply { + isRead = false + if (notify) isNotified = false + } + }, + ) } private suspend fun refreshGradeSummaries( @@ -115,31 +120,43 @@ class GradeRepository @Inject constructor( newSummary: List, notify: Boolean ) { - gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) - gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> - val oldSummary = oldSummaries.find { old -> old.subject == summary.subject } - summary.isPredictedGradeNotified = when { - summary.predictedGrade.isEmpty() -> true - notify && oldSummary?.predictedGrade != summary.predictedGrade -> false - else -> true - } - summary.isFinalGradeNotified = when { - summary.finalGrade.isEmpty() -> true - notify && oldSummary?.finalGrade != summary.finalGrade -> false - else -> true - } + gradeSummaryDb.removeOldAndSaveNew( + oldItems = oldSummaries uniqueSubtract newSummary, + newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary -> + getGradeSummaryWithUpdatedNotificationState( + summary = summary, + oldSummary = oldSummaries.find { it.subject == summary.subject }, + notify = notify, + ) + }, + ) + } - summary.predictedGradeLastChange = when { - oldSummary == null -> Instant.now() - summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() - else -> oldSummary.predictedGradeLastChange - } - summary.finalGradeLastChange = when { - oldSummary == null -> Instant.now() - summary.finalGrade != oldSummary.finalGrade -> Instant.now() - else -> oldSummary.finalGradeLastChange - } - }) + private fun getGradeSummaryWithUpdatedNotificationState( + summary: GradeSummary, + oldSummary: GradeSummary?, + notify: Boolean, + ) { + summary.isPredictedGradeNotified = when { + summary.predictedGrade.isEmpty() -> true + notify && oldSummary?.predictedGrade != summary.predictedGrade -> false + else -> true + } + summary.isFinalGradeNotified = when { + summary.finalGrade.isEmpty() -> true + notify && oldSummary?.finalGrade != summary.finalGrade -> false + else -> true + } + summary.predictedGradeLastChange = when { + oldSummary == null -> Instant.now() + summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() + else -> oldSummary.predictedGradeLastChange + } + summary.finalGradeLastChange = when { + oldSummary == null -> Instant.now() + summary.finalGrade != oldSummary.finalGrade -> Instant.now() + else -> oldSummary.finalGradeLastChange + } } fun getUnreadGrades(semester: Semester): Flow> { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 23d7b8582..809f92d3e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -19,7 +19,7 @@ import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex -import java.util.* +import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -62,8 +62,10 @@ class GradeStatisticsRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - gradePartialStatisticsDb.deleteAll(old uniqueSubtract new) - gradePartialStatisticsDb.insertAll(new uniqueSubtract old) + gradePartialStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester)) }, mapResult = { items -> @@ -80,6 +82,7 @@ class GradeStatisticsRepository @Inject constructor( ) listOf(summaryItem) + items } + else -> items.filter { it.subject == subjectName } }.mapPartialToStatisticItems() } @@ -107,8 +110,10 @@ class GradeStatisticsRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new) - gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old) + gradeSemesterStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester)) }, mapResult = { items -> @@ -138,6 +143,7 @@ class GradeStatisticsRepository @Inject constructor( } listOf(summaryItem) + itemsWithAverage } + else -> itemsWithAverage.filter { it.subject == subjectName } }.mapSemesterToStatisticItems() } @@ -163,8 +169,10 @@ class GradeStatisticsRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - gradePointsStatisticsDb.deleteAll(old uniqueSubtract new) - gradePointsStatisticsDb.insertAll(new uniqueSubtract old) + gradePointsStatisticsDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester)) }, mapResult = { items -> diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 010cf8458..1a9c7ffaf 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -61,14 +61,14 @@ class HomeworkRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - val homeWorkToSave = (new uniqueSubtract old).onEach { - if (notify) it.isNotified = false - } val filteredOld = old.filterNot { it.isAddedByUser } - homeworkDb.deleteAll(filteredOld uniqueSubtract new) - homeworkDb.insertAll(homeWorkToSave) - + homeworkDb.removeOldAndSaveNew( + oldItems = filteredOld uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 4ff4517d0..45b7f6e29 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -18,7 +18,7 @@ import javax.inject.Singleton @Singleton class LuckyNumberRepository @Inject constructor( private val luckyNumberDb: LuckyNumberDao, - private val sdk: Sdk + private val sdk: Sdk, ) { private val saveFetchResultMutex = Mutex() @@ -39,11 +39,10 @@ class LuckyNumberRepository @Inject constructor( newLuckyNumber ?: return@networkBoundResource if (newLuckyNumber != oldLuckyNumber) { - val updatedLuckNumberList = - listOf(newLuckyNumber.apply { if (notify) isNotified = false }) - - oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) } - luckyNumberDb.insertAll(updatedLuckNumberList) + luckyNumberDb.removeOldAndSaveNew( + oldItems = listOfNotNull(oldLuckyNumber), + newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }), + ) } } ) 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 96f048706..a4517760b 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 @@ -89,12 +89,13 @@ class MessageRepository @Inject constructor( }, saveFetchResult = { oldWithAuthors, new -> val old = oldWithAuthors.map { it.message } - messagesDb.deleteAll(old uniqueSubtract new) - messagesDb.insertAll((new uniqueSubtract old).onEach { - val muted = isMuted(it.correspondents) - it.isNotified = !notify || muted - }) - + messagesDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + val muted = isMuted(it.correspondents) + it.isNotified = !notify || muted + }, + ) refreshHelper.updateLastRefreshTimestamp( getRefreshKey(messagesCacheKey, mailbox, folder) ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 412f9e7f0..48b4fc287 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -48,9 +48,10 @@ class MobileDeviceRepository @Inject constructor( .mapToEntities(student) }, saveFetchResult = { old, new -> - mobileDb.deleteAll(old uniqueSubtract new) - mobileDb.insertAll(new uniqueSubtract old) - + mobileDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index eeb1d53ef..feb92c154 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -7,7 +7,12 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.switchSemester +import io.github.wulkanowy.utils.toLocalDate +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -46,14 +51,16 @@ class NoteRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - noteDb.deleteAll(old uniqueSubtract new) - noteDb.insertAll((new uniqueSubtract old).onEach { + val notesToAdd = (new uniqueSubtract old).onEach { if (it.date >= student.registrationDate.toLocalDate()) it.apply { isRead = false if (notify) isNotified = false } - }) - + } + noteDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = notesToAdd, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt index 79984ce6d..4a1474ced 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -1,7 +1,11 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.db.dao.RecipientDao -import io.github.wulkanowy.data.db.entities.* +import io.github.wulkanowy.data.db.entities.Mailbox +import io.github.wulkanowy.data.db.entities.MailboxType +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper @@ -25,8 +29,10 @@ class RecipientRepository @Inject constructor( .mapToEntities(mailbox.globalKey) val old = recipientDb.loadAll(type, mailbox.globalKey) - recipientDb.deleteAll(old uniqueSubtract new) - recipientDb.insertAll(new uniqueSubtract old) + recipientDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index 8537fbc3e..f09a46aa1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -47,12 +47,12 @@ class SchoolAnnouncementRepository @Inject constructor( lastAnnouncements + directorInformation }, saveFetchResult = { old, new -> - val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach { - if (notify) it.isNotified = false - } - - schoolAnnouncementDb.deleteAll(old uniqueSubtract new) - schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave) + schoolAnnouncementDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = (new uniqueSubtract old).onEach { + if (notify) it.isNotified = false + }, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index f757ef047..b42b4d577 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -47,10 +47,10 @@ class SchoolRepository @Inject constructor( }, saveFetchResult = { old, new -> if (old != null && new != old) { - with(schoolDb) { - deleteAll(listOf(old)) - insertAll(listOf(new)) - } + schoolDb.removeOldAndSaveNew( + oldItems = listOf(old), + newItems = listOf(new) + ) } else if (old == null) { schoolDb.insertAll(listOf(new)) } 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 dd44df70f..9ae22babc 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 @@ -5,7 +5,11 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.getCurrentOrLast +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.isCurrent +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject @@ -15,7 +19,7 @@ import javax.inject.Singleton class SemesterRepository @Inject constructor( private val semesterDb: SemesterDao, private val sdk: Sdk, - private val dispatchers: DispatchersProvider + private val dispatchers: DispatchersProvider, ) { suspend fun getSemesters( @@ -45,6 +49,7 @@ class SemesterRepository @Inject constructor( 0 == it.diaryId && 0 == it.kindergartenDiaryId } == true } + else -> false } @@ -59,8 +64,10 @@ class SemesterRepository @Inject constructor( if (new.isEmpty()) return Timber.i("Empty semester list!") val old = semesterDb.loadAll(student.studentId, student.classId) - semesterDb.deleteAll(old.uniqueSubtract(new)) - semesterDb.insertSemesters(new.uniqueSubtract(old)) + semesterDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) } suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index d6cd25c82..d42be180d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -15,7 +15,7 @@ import javax.inject.Singleton @Singleton class StudentInfoRepository @Inject constructor( private val studentInfoDao: StudentInfoDao, - private val sdk: Sdk + private val sdk: Sdk, ) { private val saveFetchResultMutex = Mutex() @@ -36,10 +36,10 @@ class StudentInfoRepository @Inject constructor( }, saveFetchResult = { old, new -> if (old != null && new != old) { - with(studentInfoDao) { - deleteAll(listOf(old)) - insertAll(listOf(new)) - } + studentInfoDao.removeOldAndSaveNew( + oldItems = listOf(old), + newItems = listOf(new), + ) } else if (old == null) { studentInfoDao.insertAll(listOf(new)) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index 98cb181af..cf7f86c22 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -45,9 +45,10 @@ class SubjectRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - subjectDao.deleteAll(old uniqueSubtract new) - subjectDao.insertAll(new uniqueSubtract old) - + subjectDao.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 42698f922..5a488b27c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -45,9 +45,10 @@ class TeacherRepository @Inject constructor( .mapToEntities(semester) }, saveFetchResult = { old, new -> - teacherDb.deleteAll(old uniqueSubtract new) - teacherDb.insertAll(new uniqueSubtract old) - + teacherDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index acbd02d18..0d208c1fc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -154,8 +154,10 @@ class TimetableRepository @Inject constructor( new.apply { if (notify) isNotified = false } } - timetableDb.deleteAll(lessonsToRemove) - timetableDb.insertAll(lessonsToAdd) + timetableDb.removeOldAndSaveNew( + oldItems = lessonsToRemove, + newItems = lessonsToAdd, + ) schedulerHelper.cancelScheduled(lessonsToRemove, student) schedulerHelper.scheduleNotifications(lessonsToAdd, student) @@ -166,13 +168,17 @@ class TimetableRepository @Inject constructor( new: List ) { val oldFiltered = old.filter { !it.isAddedByUser } - timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new) - timetableAdditionalDb.insertAll(new uniqueSubtract old) + timetableAdditionalDb.removeOldAndSaveNew( + oldItems = oldFiltered uniqueSubtract new, + newItems = new uniqueSubtract old, + ) } private suspend fun refreshDayHeaders(old: List, new: List) { - timetableHeaderDb.deleteAll(old uniqueSubtract new) - timetableHeaderDb.insertAll(new uniqueSubtract old) + timetableHeaderDb.removeOldAndSaveNew( + oldItems = old uniqueSubtract new, + newItems = new uniqueSubtract old, + ) } fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant { diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index d0e500f19..e64144c2f 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -10,11 +10,17 @@ import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -61,26 +67,36 @@ class AttendanceRepositoryTest { } @Test - fun `force refresh without difference`() { + fun `force refresh without difference`() = runTest { // prepare coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester, emptyList())), flowOf(remoteList.mapToEntities(semester, emptyList())) ) - coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { attendanceDb.deleteAll(any()) } just Runs + coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + val res = attendanceRepository.getAttendance( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + ).toFirstResult() + // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } - coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } - coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } + coVerify { + attendanceDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { it.isEmpty() }, + ) + } } @Test @@ -89,14 +105,23 @@ class AttendanceRepositoryTest { coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), - flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result + flowOf( + remoteList.dropLast(1).mapToEntities(semester, emptyList()) + ), // after fetch end before save result flowOf(remoteList.mapToEntities(semester, emptyList())) ) - coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { attendanceDb.deleteAll(any()) } just Runs + coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + val res = runBlocking { + attendanceRepository.getAttendance( + student, + semester, + startDate, + endDate, + true + ).toFirstResult() + } // verify assertEquals(null, res.errorOrNull) @@ -104,11 +129,13 @@ class AttendanceRepositoryTest { coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { - attendanceDb.insertAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] - }) + attendanceDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] + }, + ) } - coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } } @Test @@ -117,25 +144,39 @@ class AttendanceRepositoryTest { coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList.dropLast(1) coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester, emptyList())), - flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result + flowOf( + remoteList.mapToEntities( + semester, + emptyList() + ) + ), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())) ) - coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { attendanceDb.deleteAll(any()) } just Runs + coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + val res = runBlocking { + attendanceRepository.getAttendance( + student, + semester, + startDate, + endDate, + true + ).toFirstResult() + } // verify assertEquals(null, res.errorOrNull) assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } - coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { - attendanceDb.deleteAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] - }) + attendanceDb.removeOldAndSaveNew( + oldItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] + }, + newItems = emptyList(), + ) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt index c28ea304b..f8f688501 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -9,11 +9,16 @@ import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -52,46 +57,28 @@ class CompletedLessonsRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - completedLessonRepository = CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper) + completedLessonRepository = + CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper) } @Test - fun `force refresh without difference`() { + fun `force refresh without difference`() = runTest { // prepare coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)) ) - coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { completedLessonDb.deleteAll(any()) } just Runs + coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } - - // verify - assertEquals(null, res.errorOrNull) - assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getCompletedLessons(startDate, endDate) } - coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } - coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } - coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) } - } - - @Test - fun `force refresh with more items in remote`() { - // prepare - coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList - coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( - flowOf(remoteList.dropLast(1).mapToEntities(semester)), - flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result - flowOf(remoteList.mapToEntities(semester)) - ) - coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { completedLessonDb.deleteAll(any()) } just Runs - - // execute - val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } + val res = completedLessonRepository.getCompletedLessons( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + ).toFirstResult() // verify assertEquals(null, res.errorOrNull) @@ -99,15 +86,52 @@ class CompletedLessonsRepositoryTest { coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { - completedLessonDb.insertAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] - }) + completedLessonDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { it.isEmpty() }, + ) } - coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) } } @Test - fun `force refresh with more items in local`() { + fun `force refresh with more items in remote`() = runTest { + // prepare + coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList + coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(semester)), + flowOf( + remoteList.dropLast(1).mapToEntities(semester) + ), // after fetch end before save result + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs + + // execute + val res = completedLessonRepository.getCompletedLessons( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true + ).toFirstResult() + + // verify + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) + coVerify { sdk.getCompletedLessons(startDate, endDate) } + coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } + coVerify { + completedLessonDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + } + ) + } + } + + @Test + fun `force refresh with more items in local`() = runTest { // prepare coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1) coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( @@ -115,22 +139,29 @@ class CompletedLessonsRepositoryTest { flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(semester)) ) - coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { completedLessonDb.deleteAll(any()) } just Runs + coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } + val res = completedLessonRepository.getCompletedLessons( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + ).toFirstResult() // verify assertEquals(null, res.errorOrNull) assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } - coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } coVerify { - completedLessonDb.deleteAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] - }) + completedLessonDb.removeOldAndSaveNew( + oldItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }, + newItems = match { it.isEmpty() }, + ) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index fb037a87e..d1ed9ca32 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -9,11 +9,17 @@ import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -64,35 +70,42 @@ class ExamRemoteTest { flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)) ) - coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { examDb.deleteAll(any()) } just Runs + coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + val res = runBlocking { + examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() + } // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } - coVerify { examDb.insertAll(match { it.isEmpty() }) } - coVerify { examDb.deleteAll(match { it.isEmpty() }) } + coVerify { examDb.removeOldAndSaveNew(emptyList(), emptyList()) } } @Test - fun `force refresh with more items in remote`() { + fun `force refresh with more items in remote`() = runTest { // prepare coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( flowOf(remoteList.dropLast(1).mapToEntities(semester)), - flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result + flowOf( + remoteList.dropLast(1).mapToEntities(semester) + ), // after fetch end before save result flowOf(remoteList.mapToEntities(semester)) ) - coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { examDb.deleteAll(any()) } just Runs + coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + val res = examRepository.getExams( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + ).toFirstResult() // verify assertEquals(null, res.errorOrNull) @@ -100,15 +113,17 @@ class ExamRemoteTest { coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { - examDb.insertAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] - }) + examDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }, + ) } - coVerify { examDb.deleteAll(match { it.isEmpty() }) } } @Test - fun `force refresh with more items in local`() { + fun `force refresh with more items in local`() = runTest { // prepare coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList.dropLast(1) coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( @@ -116,22 +131,27 @@ class ExamRemoteTest { flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(semester)) ) - coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { examDb.deleteAll(any()) } just Runs + coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + val res = examRepository.getExams( + student = student, + semester = semester, + start = startDate, + end = endDate, + forceRefresh = true, + ).toFirstResult() // verify assertEquals(null, res.errorOrNull) assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } - coVerify { examDb.insertAll(match { it.isEmpty() }) } coVerify { - examDb.deleteAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] - }) + examDb.removeOldAndSaveNew( + oldItems = match { it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] }, + newItems = emptyList() + ) } } 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 515b0d66d..0ea5d3fa4 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 @@ -22,6 +22,7 @@ import io.mockk.impl.annotations.SpyK import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -60,26 +61,27 @@ class GradeRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - gradeRepository = - GradeRepository(gradeDb, gradeSummaryDb, gradeDescriptiveDb, sdk, refreshHelper) + gradeRepository = GradeRepository( + gradeDb = gradeDb, + gradeSummaryDb = gradeSummaryDb, + gradeDescriptiveDb = gradeDescriptiveDb, + sdk = sdk, + refreshHelper = refreshHelper, + ) - coEvery { gradeDb.deleteAll(any()) } just Runs - coEvery { gradeDb.insertAll(any()) } returns listOf() + coEvery { gradeDb.removeOldAndSaveNew(any(), any()) } just Runs + coEvery { gradeSummaryDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf( flowOf(listOf()), flowOf(listOf()), flowOf(listOf()) ) - coEvery { gradeSummaryDb.deleteAll(any()) } just Runs - coEvery { gradeSummaryDb.insertAll(any()) } returns listOf() + coEvery { gradeDescriptiveDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf( flowOf(listOf()), ) - - coEvery { gradeDescriptiveDb.deleteAll(any()) } just Runs - coEvery { gradeDescriptiveDb.insertAll(any()) } returns listOf() } @Test @@ -113,13 +115,16 @@ class GradeRepositoryTest { assertEquals(null, res.errorOrNull) assertEquals(4, res.dataOrNull?.first?.size) coVerify { - gradeDb.insertAll(withArg { - assertEquals(4, it.size) - assertTrue(it[0].isRead) - assertTrue(it[1].isRead) - assertFalse(it[2].isRead) - assertFalse(it[3].isRead) - }) + gradeDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = withArg { + assertEquals(4, it.size) + assertTrue(it[0].isRead) + assertTrue(it[1].isRead) + assertFalse(it[2].isRead) + assertFalse(it[3].isRead) + }, + ) } } @@ -167,23 +172,23 @@ class GradeRepositoryTest { assertEquals(null, res.errorOrNull) assertEquals(4, res.dataOrNull?.first?.size) coVerify { - gradeDb.insertAll(withArg { - assertEquals(3, it.size) - assertTrue(it[0].isRead) - assertTrue(it[1].isRead) - assertFalse(it[2].isRead) - assertEquals(remoteList.mapToEntities(semester).last(), it[2]) - }) - } - coVerify { - gradeDb.deleteAll(withArg { - assertEquals(2, it.size) - }) + gradeDb.removeOldAndSaveNew( + oldItems = withArg { + assertEquals(2, it.size) + }, + newItems = withArg { + assertEquals(3, it.size) + assertTrue(it[0].isRead) + assertTrue(it[1].isRead) + assertFalse(it[2].isRead) + assertEquals(remoteList.mapToEntities(semester).last(), it[2]) + } + ) } } @Test - fun `force refresh when local contains duplicated grades`() { + fun `force refresh when local contains duplicated grades`() = runTest { // prepare val remoteList = listOf( createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), @@ -203,13 +208,17 @@ class GradeRepositoryTest { ) // execute - val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + val res = gradeRepository.getGrades(student, semester, true).toFirstResult() // verify assertEquals(null, res.errorOrNull) assertEquals(2, res.dataOrNull?.first?.size) - coVerify { gradeDb.insertAll(match { it.isEmpty() }) } - coVerify { gradeDb.deleteAll(match { it.size == 1 }) } // ... here + coVerify { + gradeDb.removeOldAndSaveNew( + oldItems = match { it.size == 1 }, // ... here + newItems = emptyList() + ) + } } @Test @@ -238,8 +247,12 @@ class GradeRepositoryTest { // verify assertEquals(null, res.errorOrNull) assertEquals(3, res.dataOrNull?.first?.size) - coVerify { gradeDb.insertAll(match { it.size == 1 }) } // ... here - coVerify { gradeDb.deleteAll(match { it.isEmpty() }) } + coVerify { + gradeDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = match { it.size == 1 }, // ... here + ) + } } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index 8e2f7c6ef..dfd36ee1a 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -71,8 +71,7 @@ class GradeStatisticsRepositoryTest { flowOf(remotePartialList.mapToEntities(semester)), flowOf(remotePartialList.mapToEntities(semester)) ) - coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs + coEvery { gradePartialStatisticsDb.removeOldAndSaveNew(any(), any()) } just Runs // execute val res = runBlocking { @@ -93,8 +92,7 @@ class GradeStatisticsRepositoryTest { assertEquals("", items[2].partial?.studentAverage) coVerify { sdk.getGradesPartialStatistics(1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) } - coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } - coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } + coVerify { gradePartialStatisticsDb.removeOldAndSaveNew(emptyList(), emptyList()) } } @Test @@ -109,8 +107,7 @@ class GradeStatisticsRepositoryTest { flowOf(remotePartialList.mapToEntities(semester)), flowOf(remotePartialList.mapToEntities(semester)) ) - coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs + coEvery { gradePartialStatisticsDb.removeOldAndSaveNew(any(), any()) } just Runs // execute val res = runBlocking { @@ -131,8 +128,7 @@ class GradeStatisticsRepositoryTest { assertEquals("5.0", items[2].partial?.studentAverage) coVerify { sdk.getGradesPartialStatistics(1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) } - coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } - coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } + coVerify { gradePartialStatisticsDb.removeOldAndSaveNew(emptyList(), emptyList()) } } private fun getGradeStatisticsPartialSubject( diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index 3225c3bd2..fa78b1bd3 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -7,11 +7,16 @@ import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK +import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -53,7 +58,8 @@ class LuckyNumberRemoteTest { coEvery { luckyNumberDb.deleteAll(any()) } just Runs // execute - val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + val res = + runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify assertEquals(null, res.errorOrNull) @@ -65,19 +71,19 @@ class LuckyNumberRemoteTest { } @Test - fun `force refresh with different item on remote`() { + fun `force refresh with different item on remote`() = runTest { // prepare coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber coEvery { luckyNumberDb.load(1, date) } returnsMany listOf( flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), - flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), // after fetch end before save result + // after fetch end before save result + flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), flowOf(luckyNumber.mapToEntity(student)) ) - coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { luckyNumberDb.deleteAll(any()) } just Runs + coEvery { luckyNumberDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + val res = luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() // verify assertEquals(null, res.errorOrNull) @@ -85,13 +91,16 @@ class LuckyNumberRemoteTest { coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { - luckyNumberDb.insertAll(match { - it.size == 1 && it[0] == luckyNumber.mapToEntity(student) - }) + luckyNumberDb.removeOldAndSaveNew( + oldItems = match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student) + .copy(luckyNumber = 6666) + }, + newItems = match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student) + } + ) } - coVerify { luckyNumberDb.deleteAll(match { - it.size == 1 && it[0] == luckyNumber.mapToEntity(student).copy(luckyNumber = 6666) - }) } } @Test @@ -103,11 +112,11 @@ class LuckyNumberRemoteTest { flowOf(null), // after fetch end before save result flowOf(luckyNumber.mapToEntity(student)) ) - coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { luckyNumberDb.deleteAll(any()) } just Runs + coEvery { luckyNumberDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + val res = + runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify assertEquals(null, res.errorOrNull) @@ -115,10 +124,12 @@ class LuckyNumberRemoteTest { coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { - luckyNumberDb.insertAll(match { - it.size == 1 && it[0] == luckyNumber.mapToEntity(student) - }) + luckyNumberDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student) + } + ) } - coVerify(exactly = 0) { luckyNumberDb.deleteAll(any()) } } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index 58937e776..fbbe49345 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -113,7 +113,7 @@ class MessageRepositoryTest { } @Test - fun `get messages when fetched completely new message without notify`() = runBlocking { + fun `get messages when fetched completely new message without notify`() = runTest { coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox) every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList()) coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf( @@ -122,8 +122,7 @@ class MessageRepositoryTest { readBy = 10, ) ) - coEvery { messageDb.deleteAll(any()) } just Runs - coEvery { messageDb.insertAll(any()) } returns listOf() + coEvery { messageDb.removeOldAndSaveNew(any(), any()) } just Runs val res = repository.getMessages( student = student, @@ -134,12 +133,14 @@ class MessageRepositoryTest { ).toFirstResult() assertEquals(null, res.errorOrNull) - coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) } coVerify { - messageDb.insertAll(withArg { - assertEquals(4, it.single().messageId) - assertTrue(it.single().isNotified) - }) + messageDb.removeOldAndSaveNew( + oldItems = withArg { checkEquals(emptyList()) }, + newItems = withArg { + assertEquals(4, it.single().messageId) + assertTrue(it.single().isNotified) + }, + ) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index 1a3f96795..aa93a5e6f 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -19,7 +19,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK import io.mockk.just import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert import org.junit.Before import org.junit.Test @@ -57,42 +57,21 @@ class MobileDeviceRepositoryTest { } @Test - fun `force refresh without difference`() { + fun `force refresh without difference`() = runTest { // prepare coEvery { sdk.getRegisteredDevices() } returns remoteList coEvery { mobileDeviceDb.loadAll(student.studentId) } returnsMany listOf( flowOf(remoteList.mapToEntities(student)), flowOf(remoteList.mapToEntities(student)) ) - coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { mobileDeviceDb.deleteAll(any()) } just Runs + coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } - - // verify - Assert.assertEquals(null, res.errorOrNull) - Assert.assertEquals(2, res.dataOrNull?.size) - coVerify { sdk.getRegisteredDevices() } - coVerify { mobileDeviceDb.loadAll(1) } - coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } - coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) } - } - - @Test - fun `force refresh with more items in remote`() { - // prepare - coEvery { sdk.getRegisteredDevices() } returns remoteList - coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( - flowOf(remoteList.dropLast(1).mapToEntities(student)), - flowOf(remoteList.dropLast(1).mapToEntities(student)), // after fetch end before save result - flowOf(remoteList.mapToEntities(student)) - ) - coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { mobileDeviceDb.deleteAll(any()) } just Runs - - // execute - val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } + val res = mobileDeviceRepository.getDevices( + student = student, + semester = semester, + forceRefresh = true, + ).toFirstResult() // verify Assert.assertEquals(null, res.errorOrNull) @@ -100,15 +79,50 @@ class MobileDeviceRepositoryTest { coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { - mobileDeviceDb.insertAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] - }) + mobileDeviceDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { it.isEmpty() }, + ) } - coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) } } @Test - fun `force refresh with more items in local`() { + fun `force refresh with more items in remote`() = runTest { + // prepare + coEvery { sdk.getRegisteredDevices() } returns remoteList + coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(student)), + flowOf( + remoteList.dropLast(1).mapToEntities(student) + ), // after fetch end before save result + flowOf(remoteList.mapToEntities(student)) + ) + coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs + + // execute + val res = mobileDeviceRepository.getDevices( + student = student, + semester = semester, + forceRefresh = true, + ).toFirstResult() + + // verify + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(2, res.dataOrNull?.size) + coVerify { sdk.getRegisteredDevices() } + coVerify { mobileDeviceDb.loadAll(1) } + coVerify { + mobileDeviceDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] + }, + ) + } + } + + @Test + fun `force refresh with more items in local`() = runTest { // prepare coEvery { sdk.getRegisteredDevices() } returns remoteList.dropLast(1) coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( @@ -116,22 +130,27 @@ class MobileDeviceRepositoryTest { flowOf(remoteList.mapToEntities(student)), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(student)) ) - coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { mobileDeviceDb.deleteAll(any()) } just Runs + coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs // execute - val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } + val res = mobileDeviceRepository.getDevices( + student = student, + semester = semester, + forceRefresh = true, + ).toFirstResult() // verify Assert.assertEquals(null, res.errorOrNull) Assert.assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } - coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } coVerify { - mobileDeviceDb.deleteAll(match { - it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] - }) + mobileDeviceDb.removeOldAndSaveNew( + oldItems = match { + it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] + }, + newItems = match { it.isEmpty() }, + ) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt index ae73a7958..e608cafb1 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt @@ -69,7 +69,12 @@ class RecipientLocalTest { @Test fun `load recipients when items already in database`() { // prepare - coEvery { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") } returnsMany listOf( + coEvery { + recipientDb.loadAll( + io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, + "v4" + ) + } returnsMany listOf( remoteList.mapToEntities("v4"), remoteList.mapToEntities("v4") ) @@ -108,8 +113,7 @@ class RecipientLocalTest { emptyList(), remoteList.mapToEntities("v4") ) - coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { recipientDb.deleteAll(any()) } just Runs + coEvery { recipientDb.removeOldAndSaveNew(any(), any()) } just Runs // execute val res = runBlocking { @@ -123,8 +127,12 @@ class RecipientLocalTest { // verify assertEquals(3, res.size) coVerify { sdk.getRecipients("v4") } - coVerify { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") } - coVerify { recipientDb.insertAll(match { it.isEmpty() }) } - coVerify { recipientDb.deleteAll(match { it.isEmpty() }) } + coVerify { + recipientDb.loadAll( + io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, + "v4" + ) + } + coVerify { recipientDb.removeOldAndSaveNew(match { it.isEmpty() }, match { it.isEmpty() }) } } } 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 31098d2ef..96db8a794 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 @@ -50,13 +50,16 @@ class SemesterRepositoryTest { coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters - coEvery { semesterDb.deleteAll(any()) } just Runs - coEvery { semesterDb.insertSemesters(any()) } returns emptyList() + coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs runBlocking { semesterRepository.getSemesters(student) } - coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } - coVerify { semesterDb.deleteAll(emptyList()) } + coVerify { + semesterDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = semesters.mapToEntities(student.studentId), + ) + } } @Test @@ -71,12 +74,17 @@ class SemesterRepositoryTest { getSemesterPojo(123, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns badSemesters.mapToEntities(student.studentId) + coEvery { + semesterDb.loadAll( + student.studentId, + student.classId + ) + } returns badSemesters.mapToEntities(student.studentId) coEvery { sdk.getSemesters() } returns goodSemesters - coEvery { semesterDb.deleteAll(any()) } just Runs - coEvery { semesterDb.insertSemesters(any()) } returns listOf() + coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs - val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.HEBE.name)) } + val items = + runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.HEBE.name)) } assertEquals(2, items.size) assertEquals(0, items[0].diaryId) } @@ -99,8 +107,7 @@ class SemesterRepositoryTest { goodSemesters.mapToEntities(student.studentId) ) coEvery { sdk.getSemesters() } returns goodSemesters - coEvery { semesterDb.deleteAll(any()) } just Runs - coEvery { semesterDb.insertSemesters(any()) } returns listOf() + coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs val items = semesterRepository.getSemesters( student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) @@ -157,13 +164,16 @@ class SemesterRepositoryTest { coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters - coEvery { semesterDb.deleteAll(any()) } just Runs - coEvery { semesterDb.insertSemesters(any()) } returns listOf() + coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } - coVerify { semesterDb.deleteAll(emptyList()) } - coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } + coVerify { + semesterDb.removeOldAndSaveNew( + oldItems = emptyList(), + newItems = semesters.mapToEntities(student.studentId), + ) + } } @Test @@ -181,12 +191,17 @@ class SemesterRepositoryTest { getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)), ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semestersWithNoCurrent + coEvery { + semesterDb.loadAll( + student.studentId, + student.classId + ) + } returns semestersWithNoCurrent coEvery { sdk.getSemesters() } returns newSemesters - coEvery { semesterDb.deleteAll(any()) } just Runs - coEvery { semesterDb.insertSemesters(any()) } returns listOf() + coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs - val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } + val items = + runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } assertEquals(2, items.size) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index 92ad01b18..2a61f99ce 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -108,8 +108,7 @@ class TimetableRepositoryTest { flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)) ) - coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) - coEvery { timetableDb.deleteAll(any()) } just Runs + coEvery { timetableDb.removeOldAndSaveNew(any(), any()) } just Runs coEvery { timetableAdditionalDao.loadAll( @@ -119,12 +118,10 @@ class TimetableRepositoryTest { end = endDate ) } returns flowOf(listOf()) - coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs - coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3) + coEvery { timetableAdditionalDao.removeOldAndSaveNew(any(), any()) } just Runs coEvery { timetableHeaderDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) - coEvery { timetableHeaderDao.insertAll(emptyList()) } returns listOf(1, 2, 3) - coEvery { timetableHeaderDao.deleteAll(emptyList()) } just Runs + coEvery { timetableHeaderDao.removeOldAndSaveNew(any(), any()) } just Runs // execute val res = runBlocking { @@ -142,8 +139,12 @@ class TimetableRepositoryTest { assertEquals(2, res.dataOrNull!!.lessons.size) coVerify { sdk.getTimetable(startDate, endDate) } coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } - coVerify { timetableDb.insertAll(match { it.isEmpty() }) } - coVerify { timetableDb.deleteAll(match { it.isEmpty() }) } + coVerify { + timetableDb.removeOldAndSaveNew( + oldItems = match { it.isEmpty() }, + newItems = match { it.isEmpty() }, + ) + } } private fun createTimetableRemote( From b319bb03cd722dedd68682d9d7fae04784358bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 2 Mar 2024 17:31:44 +0100 Subject: [PATCH 421/545] New Crowdin updates (#2458) --- app/src/main/res/values-cs/strings.xml | 5 +++-- app/src/main/res/values-de/strings.xml | 5 +++-- app/src/main/res/values-pl/strings.xml | 5 +++-- app/src/main/res/values-ru/strings.xml | 5 +++-- app/src/main/res/values-sk/strings.xml | 5 +++-- app/src/main/res/values-uk/strings.xml | 5 +++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index fbc92e46f..48b43ae40 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -98,8 +98,8 @@ Přihlásit se Relace vypršela Relace vypršela. Přihlaste se prosím znovu - Heslo k vašemu účtu bylo změněno. Musíte se znovu přihlásit do Wulkanového - Heslo bylo změněno + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy again Podpora aplikace Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout Zapnout reklamy @@ -860,6 +860,7 @@ This account is inactive. Try logging in again Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později Načítání dat se nezdařilo. Prosím zkuste to znovu později + Your password has expired or been changed. Please log in again Je vyžadována změna hesla pro deník Probíhá údržba deníku UONET+. Zkuste to později znovu Neznámá chyba deniku UONET+. Prosím zkuste to znovu později diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1f1246004..ce3ab0d9b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -98,8 +98,8 @@ Anmelden Die Sitzung ist abgelaufen Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein - Your account password has been changed. You need to log in to Wulkanowy again - Password changed + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy again Anwendungsunterstützung Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können Werbung aktivieren @@ -766,6 +766,7 @@ This account is inactive. Try logging in again Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal + Your password has expired or been changed. Please log in again Passwortänderung für Registrierung erforderlich Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 597d843df..a193da1be 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -98,8 +98,8 @@ Zaloguj się Sesja wygasła Sesja wygasła, zaloguj się ponownie - Hasło do Twojego konta zostało zmienione. Musisz zalogować się ponownie do Wulkanowego - Hasło zostało zmienione + Hasło wygasło lub zostało zmienione + Hasło do twojego konta wygasło lub zostało zmienione. Musisz zalogować się ponownie do Wulkanowego Wparcie aplikacji Podoba Ci się ta aplikacja? Wspieraj jej rozwój poprzez włączenie nieinwazyjnych reklam, które możesz wyłączyć w dowolnym momencie Włącz reklamy @@ -860,6 +860,7 @@ Konto jest nieaktywne. Spróbuj zalogować się ponownie Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później Ładowanie danych nie powiodło się. Spróbuj ponownie później + Twoje hasło wygasło lub zostało zmienione. Zaloguj się ponownie Wymagana zmiana hasła do dziennika Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później Nieznany błąd dziennika UONET+. Spróbuj ponownie później diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 46a19c71f..590dc13db 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -98,8 +98,8 @@ Войти Сеанс истёк Сеанс истёк, авторизуйтесь снова - Your account password has been changed. You need to log in to Wulkanowy again - Password changed + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy again Поддержка приложения Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время Включить рекламу @@ -860,6 +860,7 @@ This account is inactive. Try logging in again Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже Не удалось загрузить данные, повторите попытку позже + Your password has expired or been changed. Please log in again Необходимо изменить пароль дневника UONET+ проводит техническое обслуживание, повторите попытку позже Неизвестная ошибка дневника UONET+, повторите попытку позже diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index b63c07c6f..93d7559af 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -98,8 +98,8 @@ Prihlásiť sa Relácia vypršala Relácia vypršala. Prihláste sa prosím znovu - Heslo k vášmu účtu bolo zmenené. Musíte sa znovu prihlásiť do Wulkanového - Heslo bolo zmenené + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy again Podpora aplikácie Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť Zapnúť reklamy @@ -860,6 +860,7 @@ This account is inactive. Try logging in again Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr Načítanie údajov zlyhalo. Skúste neskôr prosím + Your password has expired or been changed. Please log in again Je vyžadovaná zmena hesla pre denník Prebieha údržba denníka UONET+. Skúste to neskôr znova Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8116c7e48..40fc96c1e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -98,8 +98,8 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову - Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову - Пароль змінено + Password has expired or been changed + Your account password has expired or been changed. You will need to log in to Wulkanowy again Підтримка додатку Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час Увімкнути рекламу @@ -860,6 +860,7 @@ This account is inactive. Try logging in again Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше Помилка завантаження даних, спробуйте пізніше + Your password has expired or been changed. Please log in again Необхідна зміна пароля щоденника UONET+ проводить технічне осблуговування, спробуйте пізніше Невідома помилка щоденника UONET+, спробуйте пізніше From 3bab883a5692539e27d6addc45f3630a5b6f5770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 19:49:08 +0100 Subject: [PATCH 422/545] Add a message explaining the reason for the captcha to the captcha dialog (#2459) --- .../io/github/wulkanowy/data/DataModule.kt | 23 +++++++++------- .../ui/modules/captcha/CaptchaDialog.kt | 5 ++++ .../utils/WebkitCookieManagerProxy.kt | 10 ++++--- app/src/main/res/layout/dialog_captcha.xml | 27 ++++++++++++++++--- app/src/main/res/values/strings.xml | 3 ++- 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 6b6c9d329..50d6c8f9f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -38,17 +38,20 @@ internal class DataModule { @Singleton @Provides - fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) = - Sdk().apply { - androidVersion = android.os.Build.VERSION.RELEASE - buildTag = android.os.Build.MODEL - userAgentTemplate = remoteConfig.userAgentTemplate - setSimpleHttpLogger { Timber.d(it) } - setAdditionalCookieManager(WebkitCookieManagerProxy()) + fun provideSdk( + chuckerInterceptor: ChuckerInterceptor, + remoteConfig: RemoteConfigHelper, + webkitCookieManagerProxy: WebkitCookieManagerProxy, + ) = Sdk().apply { + androidVersion = android.os.Build.VERSION.RELEASE + buildTag = android.os.Build.MODEL + userAgentTemplate = remoteConfig.userAgentTemplate + setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(webkitCookieManagerProxy) - // for debug only - addInterceptor(chuckerInterceptor, network = true) - } + // for debug only + addInterceptor(chuckerInterceptor, network = true) + } @Singleton @Provides diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt index ed8293a9f..98b4fda71 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -13,6 +13,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogCaptchaBinding import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseDialogFragment +import io.github.wulkanowy.utils.WebkitCookieManagerProxy import timber.log.Timber import javax.inject.Inject @@ -22,6 +23,9 @@ class CaptchaDialog : BaseDialogFragment() { @Inject lateinit var sdk: Sdk + @Inject + lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy + private var webView: WebView? = null companion object { @@ -80,6 +84,7 @@ class CaptchaDialog : BaseDialogFragment() { } override fun onDestroy() { + webkitCookieManagerProxy.webkitCookieManager?.flush() webView?.destroy() super.onDestroy() } diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt index 3d41c711c..4d2dde788 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -5,17 +5,21 @@ import java.net.CookiePolicy import java.net.CookieStore import java.net.HttpCookie import java.net.URI +import javax.inject.Inject +import javax.inject.Singleton import android.webkit.CookieManager as WebkitCookieManager import java.net.CookieManager as JavaCookieManager -class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { +@Singleton +class WebkitCookieManagerProxy @Inject constructor() : + JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { - private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager() + val webkitCookieManager: WebkitCookieManager? = getCookieManager() /** * @see [https://stackoverflow.com/a/70354583/6695449] */ - private fun getWebkitCookieManager(): WebkitCookieManager? { + private fun getCookieManager(): WebkitCookieManager? { return try { WebkitCookieManager.getInstance() } catch (e: AndroidRuntimeException) { diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml index 539aa0cc9..019d89327 100644 --- a/app/src/main/res/layout/dialog_captcha.xml +++ b/app/src/main/res/layout/dialog_captcha.xml @@ -7,15 +7,18 @@ tools:context=".ui.modules.captcha.CaptchaDialog"> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0" /> + + + + + app:layout_constraintTop_toBottomOf="@id/captcha_webview" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 266c3522c..2775365d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -848,7 +848,8 @@ - Verification is in progress. Wait… + VULCAN\'s website requires verification + Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it Verified successfully From a0a0b8dea6e70b32e310158caeee317026326bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 2 Mar 2024 20:37:36 +0100 Subject: [PATCH 423/545] New Crowdin updates (#2460) --- app/src/main/res/values-cs/strings.xml | 27 +++++++++++++------------- app/src/main/res/values-de/strings.xml | 3 ++- app/src/main/res/values-pl/strings.xml | 3 ++- app/src/main/res/values-ru/strings.xml | 3 ++- app/src/main/res/values-sk/strings.xml | 27 +++++++++++++------------- app/src/main/res/values-uk/strings.xml | 27 +++++++++++++------------- 6 files changed, 48 insertions(+), 42 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 48b43ae40..c3c691c7f 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -56,7 +56,7 @@ Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Použijte přiřazené přihlašovací nebo e-mail v @%1$s - Invalid domain suffix + Neplatná přípona domény Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ @@ -98,8 +98,8 @@ Přihlásit se Relace vypršela Relace vypršela. Přihlaste se prosím znovu - Password has expired or been changed - Your account password has expired or been changed. You will need to log in to Wulkanowy again + Heslo vypršelo nebo bylo změněno + Platnost hesla k vašemu účtu vypršela nebo bylo změněno. Budete se muset znovu přihlásit do Wulkanového Podpora aplikace Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout Zapnout reklamy @@ -336,10 +336,10 @@ Poslat dále Vybrat vše Odznačit vše - Restore from trash + Obnovit z koše Přesunout do koše Odstranit natrvalo - Message restored successfully + Zpráva úspěšně obnovena Zpráva byla úspěšně odstraněna žák rodič @@ -385,7 +385,7 @@ %1$d vybraných Zprávy odstraněné - Messages restored + Obnovené zprávy 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 @@ -852,15 +852,16 @@ 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 - Probíhá ověřování. Počkejte… + VULCAN\'s website requires verification + Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it Úspěšně ověřeno Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení - This account is inactive. Try logging in again + Tento účet je neaktivní. Zkuste se znovu přihlásit Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později Načítání dat se nezdařilo. Prosím zkuste to znovu později - Your password has expired or been changed. Please log in again + Vaše heslo vypršelo nebo bylo změněno. Přihlaste se znovu Je vyžadována změna hesla pro deník Probíhá údržba deníku UONET+. Zkuste to později znovu Neznámá chyba deniku UONET+. Prosím zkuste to znovu později @@ -871,8 +872,8 @@ Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API Toto pole je povinné - Mute - Unmute - You have muted this user - You have unmuted this user + Ztlumit + Zrušit ztlumení + Ztlumili jste tohoto uživatele + Zrušili jste ztlumení tohoto uživatele diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ce3ab0d9b..daabc7d8f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -758,7 +758,8 @@ 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 - Verification is in progress. Wait… + VULCAN\'s website requires verification + Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it Verified successfully Keine Internetverbindung diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a193da1be..33b715d75 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -852,7 +852,8 @@ Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej Na razie pomiń - Trwa weryfikacja. Czekaj… + Strona dziennika VULCAN wymaga weryfikacji + Dlaczego to widzę?\nStrona internetowa dziennika, z której Wulkanowy pobiera dane, wyświetla ten sam ekran jak powyżej, więc Wulkanowy musi również ją pokazać, aby móc pobrać dane z tej witryny. Nie da się tego obejść Pomyślnie zweryfikowano Brak połączenia z internetem diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 590dc13db..8a5fcc40d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -852,7 +852,8 @@ Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже Пропустить сейчас - Verification is in progress. Wait… + VULCAN\'s website requires verification + Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it Verified successfully Интернет-соединение отсутствует diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 93d7559af..829475d60 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -56,7 +56,7 @@ Neplatný e-mail Namiesto e-mailu použite priradené prihlasovacie údaje Použite priradené prihlasovacie alebo e-mail v @%1$s - Invalid domain suffix + Neplatná prípona domény Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ @@ -98,8 +98,8 @@ Prihlásiť sa Relácia vypršala Relácia vypršala. Prihláste sa prosím znovu - Password has expired or been changed - Your account password has expired or been changed. You will need to log in to Wulkanowy again + Heslo vypršalo alebo bolo zmenené + Platnosť hesla k vášmu účtu vypršala alebo bolo zmenené. Budete sa musieť znova prihlásiť do Wulkanového Podpora aplikácie Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť Zapnúť reklamy @@ -336,10 +336,10 @@ Poslať ďalej Vybrať všetko Odznačiť všetko - Restore from trash + Obnoviť z koša Presunúť do koša Odstrániť natrvalo - Message restored successfully + Správa úspešne obnovená Správa bola úspešne odstránená žiak rodič @@ -385,7 +385,7 @@ %1$d vybraných Správy odstránené - Messages restored + Obnovené správy 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 @@ -852,15 +852,16 @@ 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ť - Overovanie prebieha. Počkajte… + VULCAN\'s website requires verification + Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it Úspešne overené Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia - This account is inactive. Try logging in again + Tento účet je neaktívny. Skúste sa znova prihlásiť Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr Načítanie údajov zlyhalo. Skúste neskôr prosím - Your password has expired or been changed. Please log in again + Vaše heslo vypršalo alebo bolo zmenené. Prihláste sa znova Je vyžadovaná zmena hesla pre denník Prebieha údržba denníka UONET+. Skúste to neskôr znova Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr @@ -871,8 +872,8 @@ Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API Toto pole je povinné - Mute - Unmute - You have muted this user - You have unmuted this user + Stlmiť + Zrušiť stlmenie + Stlmili ste tohto používateľa + Zrušili ste stlmenie tohto používateľa diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 40fc96c1e..a0d4b6c0b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -56,7 +56,7 @@ Недійсна адреса e-mail Використовуйте призначений логін замість адреси e-mail Використовуйте призначений логін або адресу e-mail в @%1$s - Invalid domain suffix + Невірний суфікс домену Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+ @@ -98,8 +98,8 @@ Увійти Минув термін дії сесії Минув термін дії сесії, авторизуйтеся знову - Password has expired or been changed - Your account password has expired or been changed. You will need to log in to Wulkanowy again + Термін дії пароля закінчився або його було змінено + Термін дії пароля для вашого облікового запису закінчився або було змінено. Необхідно зайти в Wulkanowy знову Підтримка додатку Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час Увімкнути рекламу @@ -336,10 +336,10 @@ Переслати Вибрати всі Відмінити вибір - Restore from trash + Відновити зі смітника Перемістити до кошика Видалити назавжди - Message restored successfully + Повідомлення успішно відновлено Лист було успішно видалено учень родич @@ -385,7 +385,7 @@ %1$d вибрано Листи видалено - Messages restored + Повідомлення відновлені Вибрати поштову скриньку Режим анонімності включено Завдяки режиму анонімності, відправник не буде сповіщений коли ви прочитаєте повідомлення @@ -852,15 +852,16 @@ Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче Поки що пропустити - Верифікація в процесі. Чекайте… + Веб-сайт VULCAN потребує підтвердження + Чому я це бачу?\nСайт реєстру, з якого Wulkanowy завантажує дані, відображає той самий екран, що й вище, тому Wulkanowy також повинен показувати його, щоб мати змогу завантажувати дані з цього сайту. Це неможливо обійти Верифікація завершена Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою - This account is inactive. Try logging in again + Цей обліковий запис неактивний. Спробуйте увійти ще раз Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше Помилка завантаження даних, спробуйте пізніше - Your password has expired or been changed. Please log in again + Термін дії вашого пароля минув або був змінений. Будь ласка увійдіть знову Необхідна зміна пароля щоденника UONET+ проводить технічне осблуговування, спробуйте пізніше Невідома помилка щоденника UONET+, спробуйте пізніше @@ -871,8 +872,8 @@ Функція недоступна в режимі Mobile API. Увійдіть в інший режим Це поле обовʼязкове - Mute - Unmute - You have muted this user - You have unmuted this user + Вимкнути сповіщення + Ввімкнути сповіщення + Ви ігноруєте цього користувача + Ви не ігноруєте цього користувача From 2bbc157d0361b0f23c1d37b174e7e60f0e3b3c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 20:45:23 +0100 Subject: [PATCH 424/545] Add some new symbols to symbol autocomplete field (#2461) --- app/src/main/res/values/api_symbols.xml | 276 ++++++++++++++++++++++++ 1 file changed, 276 insertions(+) diff --git a/app/src/main/res/values/api_symbols.xml b/app/src/main/res/values/api_symbols.xml index 4b61db48d..510995b9d 100644 --- a/app/src/main/res/values/api_symbols.xml +++ b/app/src/main/res/values/api_symbols.xml @@ -1,6 +1,8 @@ + Adamów, powiat łukowski + Aleksandrów, powiat biłgorajski Andrychów Augustów Baranów Sandomierski @@ -8,6 +10,8 @@ Bełchatów Bełżyce Biała Podlaska + Biała, powiat prudnicki + Biała, powiat wielunski Biała Rawska Biały Bór Białystok @@ -23,11 +27,17 @@ Boguchwała Boguty-Pianki Bolesławiec + Bolesław, powiat dąbrowski Braniewo Brodnica + Brodnica, powiat śremski + Brody, powiat starachowicki + Brójce, powiat łódzki wschodni Brwinów Brzeg Brzeski + Powiat brzeski + Brzeźnica, powiat wadowicki Buk Bukowno Busko-Zdrój @@ -36,6 +46,7 @@ Bystrzyca Kłodzka Bytom Bytom Odrzański + CECH bialski Chełm Chełmno Chełmża @@ -44,18 +55,28 @@ Chojnice Chojnów Chorzów + Chrzanów, powiat janowski Ciechanów Cieszyn + Czarna, powiat bieszczadzki + Czarna, powiat dębicki Czarnków Czeladź + Czermin, powiat mielecki + Czermin, powiat pleszewski Czersk Częstochowa Człuchów + Dąbie, powiat krośnieński Dąbrowa Białostocka Dąbrowa Górnicza + Dabrowa, powiat opolski Dąbrowa Tarnowska Dębica Dębno + Dębowiec, powiat jasielski + Dobra, powiat lobeski + Dobre, powiat radziejowski Dobrzeń Wielki Dobrzeń Wielki 2 Dobrzyń Nad Wisłą @@ -67,6 +88,8 @@ Elbląg Ełk Frampol + Fundacja Elementarz + Fundacja Mozaika Garwolin Gdańsk Gdynia @@ -124,6 +147,7 @@ Gmina Brańszczyk Gmina Brąszewice Gmina Brenna + Gmina Brochów Gmina Brok Gmina Brzeg Dolny Gmina Brzeziny @@ -221,6 +245,7 @@ Gmina Działoszyce Gmina Dziemiany Gmina Dzierżoniów + Gmina Dziwnów Gmina Dzwola Gmina Elbląg Gmina Ełk @@ -296,6 +321,7 @@ Gmina Hrubieszów Gmina Huszlew Gmina Hyżne + Gmina Igołomia-Wawrzeńczyce Gmina Imielno Gmina Inowrocław Gmina Irządze @@ -339,6 +365,7 @@ Gmina Kamienica Gmina Kamiennik Gmina Kamionka + Gmina Kampinos Gmina Karczmiska Gmina Kargowa Gmina Karlino @@ -410,6 +437,7 @@ Gmina Krasocin Gmina Krempna Gmina Krokowa + Gmina Krościenko Gmina Krośnice Gmina Krupski Młyn Gmina Kruszwica @@ -477,6 +505,7 @@ Gmina Łopiennik Górny Gmina Łopuszno Gmina Łosice + Gmina Łososina Dolna Gmina Lubań Gmina Lubartów Gmina Lubasz @@ -524,6 +553,7 @@ Gmina Miejsce Piastowe Gmina Miękinia Gmina Mielec + Gmina Mieleszyn Gmina Mielno Gmina Mieszkowice Gmina Milanów @@ -575,6 +605,7 @@ Gmina Nowy Kawęczyn Gmina Nowy Korczyn Gmina Nowy Staw + Gmina Nowy Wiśnicz Gmina Nowy Targ Gmina Nowy Tomyśl Gmina Nozdrzec @@ -587,6 +618,7 @@ Gmina Olszyna Gmina Opatowiec Gmina Orneta + Gmina Orchowo Gmina Osieczna Gmina Osiek Gmina Osiek Jasielski @@ -629,9 +661,12 @@ Gmina Piątnica Gmina Piekoszów Gmina Pieniężno + Gmina Pietrowice Wielkie Gmina Pilchowice + Gmina Pielgrzymka Gmina Pińczów Gmina Pionki + Gmina Piszczac Gmina Płaska Gmina Platerówka Gmina Pleśna @@ -655,6 +690,7 @@ Gmina Popów Gmina Potęgowo Gmina Potok Wielki + Gmina Paradyż Gmina Praszka Gmina Prochowice Gmina Promna @@ -733,6 +769,7 @@ Gmina Sanok Gmina Sawin Gmina Ścinawa + Gmina Secemin Gmina Sędziejowice Gmina Sejny Gmina Sękowa @@ -758,6 +795,7 @@ Gmina Sitno Gmina Skarżysko Kościelne Gmina Skępe + Gmina Skierbieszów Gmina Skierniewice Gmina Skoczów Gmina Skoki @@ -779,6 +817,7 @@ Gmina Sobótka Gmina Sokółka Gmina Solina + Gmina Somonino Gmina Sośnicowice Gmina Sośnie Gmina Sośno @@ -800,11 +839,13 @@ Gmina Stoczek Łukowski Gmina Stopnica Gmina Strawczyn + Gmina Stromiec Gmina Stryków Gmina Stryszawa Gmina Stryszów Gmina Strzałkowo Gmina Strzelce Opolskie + Gmina Strzelce Opolskie 2 Gmina Strzelin Gmina Strzelno Gmina Strzyżewice @@ -846,6 +887,7 @@ Gmina Tarnów Gmina Tarnowiec Gmina Tarnów Opolski + Gmina Tarnów Opolski 2 Gmina Teresin Gmina Tereszpol Gmina Tłuchowo @@ -870,6 +912,7 @@ Gmina Tyrawa Wołoska Gmina Uchanie Gmina Ujazd + Gmina PSP Ujazd Gmina Ulan-Majorat Gmina Ulanów Gmina Ułęż @@ -898,6 +941,7 @@ Gmina Wielgomłyny Gmina Wieliszew Gmina Wielka Nieszawka + Gmina Gmina Wielopole Skrzyńskie Gmina Wieniawa Gmina Wieprz Gmina Wieruszów @@ -937,6 +981,7 @@ Gmina Wolsztyn Gmina Wręczyca Wielka Gmina Wronki + Gmina Wyryki Gmina Wyrzysk Gmina Wysokie Gmina Żabno @@ -980,6 +1025,7 @@ Gmina Żołynia Gmina Żukowice Gmina Żurawica + Gmina Żychlin Gmina Żyraków Gmina Żyrzyn Gmina Żytno @@ -992,9 +1038,11 @@ Górzno Gorzów Śląski Gorzów Wielkopolski + Gorzyce powiat tarnobrzeski Gostynin Grajewo Grodzisk Mazowiecki + Grodzisk Wielkopolski Grudziądz Grybów Gryfino @@ -1002,10 +1050,14 @@ Hel Hrubieszów Inowrocław + IR Tarnów Izbica Kujawska + Jabłonna, powiat legionowski + Jabłonna, powiat lubelski Jabłonowo Pomorskie Janowiec Wielkopolski Janów Lubelski + Janów, powiat sokolski Jarocin Jarosław Jasło @@ -1040,6 +1092,7 @@ Kołobrzeg Koniecpol Konin + Konopnica powiat lubelski Konstancin-Jeziorna Konstantynów Łódzki Koronowo @@ -1060,6 +1113,7 @@ Krosno Krotoszyce Krotoszyn + Krynica Krzeszowice Krzyż Wielkopolski Książ Wielkopolski @@ -1079,12 +1133,14 @@ Lędziny Legionowo Legnica + Leśnica opolska Leszno Lewin Brzeski Lewin Brzeski 2 Leżajsk Limanowa Lipno + Lipno, powiat lipnowski Łódź Łódzkie Łowicz @@ -1095,6 +1151,7 @@ Lubin Lublin Lubliniec + Lubnice, powiat staszowski Lubuskie Łuków Lwówecki @@ -1102,18 +1159,30 @@ Malbork Małopolskie Marki + Maszewo, powiat goleniowski Mazowieckie + MEN + Miasto Toruń Michałowice Miechów Międzyrzec Podlaski Miejska Górka Mielec Milanówek + Ministerstwo Rolnictwa Mińsk Mazowiecki + Ministerstwo Kultury i Dziedzictwa Narodowego Mniszków + Ministerstwo Nauki i Szkolnictwa + Ministerstwo Obrony Narodowej + Ministerstwo Środowiska Mosina + Moszczenica powiat gorlicki + Moszczenica powiat piotrkowski Mrągowo Mrągowski + Ministerstwo Sprawiedliwości + Ministerstwo Spraw Wewnętrznych Mszana Dolna Mszczonów Muszyna @@ -1137,9 +1206,17 @@ Nowy Żmigród Nysa Oborniki Śląskie + Oborniki Wielkopolskie Obrzycko + Oleśnica, powiat olesnicki + Oleśnica, powiat staszowski + Olesno powiat dąbrowski + Olesno powiat oleski Olkusz + Olszanka, powiat brzeski Olsztyn + Opatów, powiat kłobucki + Opatów, powiat opatowski Opinogóra Górna Opoczno Opole @@ -1148,8 +1225,10 @@ Orzesze Osieczna Osiecznica + Osiek, powiat starogardzki Ostróda Ostrołęka + Ostrowiec Świętokrzyski Ostrów Wielkopolski Oświęcim Otwock @@ -1166,6 +1245,7 @@ Pilzno Piotrków Trybunalski Pisz + Piwniczna Płock Płońsk Pniewy @@ -1177,6 +1257,8 @@ Pomorskie Poniec Poręba + Poświętne, powiat opoczyński + Poświętne, powiat wołomiński Powiat aleksandrowski Powiat augustowski Powiat będziński @@ -1217,6 +1299,7 @@ Powiat giżycki Powiat gliwicki Powiat głogowski + Powiat głubczycki Powiat gnieźnieński Powiat gołdapski Powiat goleniowski @@ -1226,6 +1309,8 @@ Powiat gorzowski Powiat gostyński Powiat grajewski + Powiat grodziski, mazowieckie + Powiat grodziski, wielkopolskie Powiat grójecki Powiat gryficki Powiat gryfiński @@ -1293,6 +1378,7 @@ Powiat makowski Powiat malborski Powiat miechowski + Powiat międzyrzecki Powiat mielecki Powiat mikołowski Powiat milicki @@ -1321,17 +1407,21 @@ Powiat olsztyński Powiat opatowski Powiat opoczyński + Powiat opole lubelskie Powiat opolski + Powiat opolski 2 Powiat ostródzki Powiat ostrowiecki Powiat ostrzeszowski Powiat oświęcimski + Powiat ostrowski, mazowieckie Powiat otwocki Powiat pabianicki Powiat piaseczyński Powiat pilski Powiat pińczowski Powiat piotrkowski + Powiat piski, warmińsko-mazurskie Powiat pleszewski Powiat płocki Powiat płoński @@ -1391,6 +1481,7 @@ Powiat suski Powiat świdnicki Powiat świdwiński + Powiat świdnicki w Świdniku Powiat świebodziński Powiat świecki Powiat szamotulski @@ -1403,6 +1494,7 @@ Powiat tatrzański Powiat tczewski Powiat tomaszowski + Powiat tomaszowski, lubelskie Powiat toruński Powiat trzebnicki Powiat tucholski @@ -1444,6 +1536,7 @@ Powiat żyrardowski Powiat żywiecki Poznań + prfrawamaz Proszowice Prudnik Pruszcz Gdański @@ -1461,6 +1554,7 @@ Rabka-Zdrój Raciąż Racibórz + Radków Kłodzki Radom Radomsko Radomyśl Wielki @@ -1468,12 +1562,17 @@ Radziejów Radzionków Radzyń Podlaski + Rakoniewice Rawa Mazowiecka Rawicz Reda + Rejowiec, powiat chełmski + Rogowo, powiat rypiński + Rogowo, powiat żniński Rogóźno Ropczyce Ruda Śląska + Rudnik, powiat raciborski Rumia Rybnik Rychwał @@ -1482,6 +1581,7 @@ Rypin Rzeszów Rzeszów projekt + Rzgów, powiat koniński Sandomierz Sanok Sędziszów Małopolski @@ -1503,14 +1603,19 @@ Sokołów Podlaski Sopot Sosnowiec + spmajkowskarzysko + spteodory Śrem Środa Śląska Środa Wielkopolska Starachowice Stargard Starogard Gdański + starostwokrosnienskie Stary Sącz Staszów + stezycapowiatrycki + stowarzyszenieintegracja Stronie Śląskie Strzyżów Sulejówek @@ -1518,9 +1623,12 @@ Sulmierzyce Swarzędz Świdnica + swidnicapowiatswidnicki + swidnicapowiatzielonogorski Świdnik Świdwin Świeradów-Zdrój + swietajnopowiatszczycienski Świętochłowice Świnoujście Syców @@ -1532,6 +1640,7 @@ Szprotawa Sztum Szubin + szydlowopowiatpilski Tarnobrzeg Tarnów Tarnowskie Góry @@ -1548,6 +1657,7 @@ Turawa Tuszyn Tychy + UG Gołcza Ujazd Ustka Ustroń @@ -1557,6 +1667,20 @@ Wałcz Warmińsko-Mazurskie Warszawa + Warszawa Bemowo + Warszawa Białołęka + Warszawa Bielany + Warszawa Mokotów + Warszawa Praga Południe + Warszawa Śródmiśscie + Warszawa Targówek + Warszawa Ursus + Warszawa Ursynow + Warszawa Wawer + Warszawa Wesoła + Warszawa Włochy + Warszawa Wola + Warszawa Żoliborz Wąsosz Węgrów Wejherowo @@ -1564,6 +1688,10 @@ Wieliczka Wielkopolskie Wieluń + Wierzbica, powiat chełmski + Wierzbica, powiat radomski + Wilków, powiat namysłowski + Wiśniowa, powiat myślenicki Władysławowo Włocławek Włodawa @@ -1580,24 +1708,36 @@ Żagań Zakliczyn Zakopane + Zakrzewo, powiat aleksandrowski Zambrów Zamość Żary Zawidów Zduńska Wola Zduny + ZDZ Warszawa Żelechów + Zespół Szkół PPC Kumarszew Zgierz Zgorzelec Zielona Góra Zielonka + ZKSO 1 Katowice Złotoryja Złotów Żory + ZS2 Lubin + ZSK Sieradz + ZSKZ Kwidzyn + ZSKZ Sochaczew + ZSP Stare Koźle + ZST powiat opoczyński Zwoleń Żyrardów + adamowpowiatlukowski + aleksandrowpowiatbilgorajski andrychow augustow baranowsandomierski @@ -1605,6 +1745,8 @@ belchatow belzyce bialapodlaska + bialapowiatprudnicki + bialapowiatwielunski bialarawska bialybor bialystok @@ -1620,11 +1762,17 @@ boguchwala bogutypianki boleslawiec + boleslawpowiatdabrowski braniewo brodnica + brodnicapowiatsremski + brodypowiatstarachowicki + brojcepowiatlodzkiwsch brwinow brzeg brzeski + brzeskipowiat + brzeznicapowiatwadowicki buk bukowno buskozdroj @@ -1633,6 +1781,7 @@ bystrzycaklodzka bytom bytomodrzanski + cechbialski chelm chelmno chelmza @@ -1641,18 +1790,28 @@ chojnice chojnow chorzow + chrzanowpowiatjanowski ciechanow cieszyn + czarnapowiatbieszczadzki + czarnapowiatdebicki czarnkow czeladz + czerminpowiatmielecki + czerminpowiatpleszewski czersk czestochowa czluchow + dabiepowiatkrosnienski dabrowabialostocka dabrowagornicza + dabrowapowiatopolski dabrowatarnowska debica debno + debowiecpowiatjasielski + dobrapowiatlobeski + dobrepowiatradziejowski dobrzenwielki dobrzenwielki2 dobrzynnadwisla @@ -1664,6 +1823,8 @@ elblag elk frampol + fundacjaelementarz + fundacjamozaika garwolin gdansk gdynia @@ -1721,6 +1882,7 @@ gminabranszczyk gminabraszewice gminabrenna + gminabrochow gminabrok gminabrzegdolny gminabrzeziny @@ -1818,6 +1980,7 @@ gminadzialoszyce gminadziemiany gminadzierzoniow + gminadziwnow gminadzwola gminaelblag gminaelk @@ -1893,6 +2056,7 @@ gminahrubieszow gminahuszlew gminahyzne + gminaiglomniawawrzenczyce gminaimielno gminainowroclaw gminairzadze @@ -1936,6 +2100,7 @@ gminakamienica gminakamiennik gminakamionka + gminakampinos gminakarczmiska gminakargowa gminakarlino @@ -2007,6 +2172,7 @@ gminakrasocin gminakrempna gminakrokowa + gminakroscienko gminakrosnice gminakrupskimlyn gminakruszwica @@ -2074,6 +2240,7 @@ gminalopiennikgorny gminalopuszno gminalosice + gminalososinadolna gminaluban gminalubartow gminalubasz @@ -2121,6 +2288,7 @@ gminamiejscepiastowe gminamiekinia gminamielec + gminamieleszyn gminamielno gminamieszkowice gminamilanow @@ -2172,6 +2340,7 @@ gminanowykaweczyn gminanowykorczyn gminanowystaw + gminanowyswisnicz gminanowytarg gminanowytomysl gminanozdrzec @@ -2184,6 +2353,7 @@ gminaolszyna gminaopatowiec gminaorneta + gminaorochowo gminaosieczna gminaosiek gminaosiekjasielski @@ -2226,9 +2396,12 @@ gminapiatnica gminapiekoszow gminapieniezno + gminapietrowicewlk gminapilchowice + gminapilelgrzymka gminapinczow gminapionki + gminapiszac gminaplaska gminaplaterowka gminaplesna @@ -2252,6 +2425,7 @@ gminapopow gminapotegowo gminapotokwielki + gminapradyz gminapraszka gminaprochowice gminapromna @@ -2330,6 +2504,7 @@ gminasanok gminasawin gminascinawa + gminasecemin gminasedziejowice gminasejny gminasekowa @@ -2355,6 +2530,7 @@ gminasitno gminaskarzyskokoscielne gminaskepe + gminaskierbieszow gminaskierniewice gminaskoczow gminaskoki @@ -2376,6 +2552,7 @@ gminasobotka gminasokolka gminasolina + gminasomonino gminasosnicowice gminasosnie gminasosno @@ -2397,11 +2574,13 @@ gminastoczeklukowski gminastopnica gminastrawczyn + gminastromiec gminastrykow gminastryszawa gminastryszow gminastrzalkowo gminastrzelceopolskie + gminastrzelceopolskie2 gminastrzelin gminastrzelno gminastrzyzewice @@ -2443,6 +2622,7 @@ gminatarnow gminatarnowiec gminatarnowopolski + gminatarnowopolski2 gminateresin gminatereszpol gminatluchowo @@ -2467,6 +2647,7 @@ gminatyrawawoloska gminauchanie gminaujazd + gminaujazdpsp gminaulanmajorat gminaulanow gminaulez @@ -2495,6 +2676,7 @@ gminawielgomlyny gminawieliszew gminawielkanieszawka + gminawielopoleskrzynskie gminawieniawa gminawieprz gminawieruszow @@ -2534,6 +2716,7 @@ gminawolsztyn gminawreczycawielka gminawronki + gminawyrki gminawyrzysk gminawysokie gminazabno @@ -2577,6 +2760,7 @@ gminazolynia gminazukowice gminazurawica + gminazychlin gminazyrakow gminazyrzyn gminazytno @@ -2589,9 +2773,11 @@ gorzno gorzowslaski gorzowwielkopolski + gorzycepowiattarnobrzeski gostynin grajewo grodziskmazowiecki + grodziskwielkopolski grudziadz grybow gryfino @@ -2599,10 +2785,14 @@ hel hrubieszow inowroclaw + irtarnow izbicakujawska + jablonnapowiatlegionowski + jablonnapowiatlubelski jablonowopomorskie janowiecwielkopolski janowlubelski + janowpowiatsokolski jarocin jaroslaw jaslo @@ -2637,6 +2827,7 @@ kolobrzeg koniecpol konin + konopnicapowiatlubelski konstancinjeziorna konstantynowlodzki koronowo @@ -2657,6 +2848,7 @@ krosno krotoszyce krotoszyn + krynica krzeszowice krzyzwielkopolski ksiazwielkopolski @@ -2676,12 +2868,14 @@ ledziny legionowo legnica + lesnicaopolska leszno lewinbrzeski lewinbrzeski2 lezajsk limanowa lipno + lipnopowiatlipnowski lodz lodzkie lowicz @@ -2692,6 +2886,7 @@ lubin lublin lubliniec + lubnicepowiatstaszowski lubuskie lukow lwowecki @@ -2699,18 +2894,30 @@ malbork malopolskie marki + maszewopowiatgoleniowski mazowieckie + men + miastotorun michalowice miechow miedzyrzecpodlaski miejskagorka mielec milanowek + minrol minskmazowiecki + mkdn mniszkow + mnsw + mon + mos mosina + moszczenicapowiatgorlicki + moszczenicapowiatpiotrkowski mragowo mragowski + ms + msw mszanadolna mszczonow muszyna @@ -2734,9 +2941,17 @@ nowyzmigrod nysa obornikislaskie + obornikiwielkopolskie obrzycko + olesnicapowiatolesnicki + olesnicapowiatstaszowski + olesnopowiatdabrowski + olesnopowiatoleski olkusz + olszankapowiatbrzeski olsztyn + opatowpowiatklobucki + opatowpowiatopatowski opinogoragorna opoczno opole @@ -2745,8 +2960,10 @@ orzesze osieczna osiecznica + osiekpowiatstarogardzki ostroda ostroleka + ostrowiecsw ostrowwielkopolski oswiecim otwock @@ -2763,6 +2980,7 @@ pilzno piotrkowtrybunalski pisz + piwniczna plock plonsk pniewy @@ -2774,6 +2992,8 @@ pomorskie poniec poreba + poswietnepowiatopoczynski + poswietnepowiatwolominski powiataleksandrowski powiataugustowski powiatbedzinski @@ -2814,6 +3034,7 @@ powiatgizycki powiatgliwicki powiatglogowski + powiatglubczycki powiatgnieznienski powiatgoldapski powiatgoleniowski @@ -2823,6 +3044,8 @@ powiatgorzowski powiatgostynski powiatgrajewski + powiatgrodziskimazowieckie + powiatgrodziskiwielkopolskie powiatgrojecki powiatgryficki powiatgryfinski @@ -2890,6 +3113,7 @@ powiatmakowski powiatmalborski powiatmiechowski + powiatmiedzyrzecki powiatmielecki powiatmikolowski powiatmilicki @@ -2918,17 +3142,21 @@ powiatolsztynski powiatopatowski powiatopoczynski + powiatopolelubelskie powiatopolski + powiatopolski2 powiatostrodzki powiatostrowiecki powiatostrzeszowski powiatoswiecimski + powiatostrowskimazowieckie powiatotwocki powiatpabianicki powiatpiaseczynski powiatpilski powiatpinczowski powiatpiotrkowski + powiatpiskiwarminskomazurskie powiatpleszewski powiatplocki powiatplonski @@ -2988,6 +3216,7 @@ powiatsuski powiatswidnicki powiatswidwinski + powiatswidnickiwswidniku powiatswiebodzinski powiatswiecki powiatszamotulski @@ -3000,6 +3229,7 @@ powiattatrzanski powiattczewski powiattomaszowski + powiattomaszowskilubelskie powiattorunski powiattrzebnicki powiattucholski @@ -3041,6 +3271,7 @@ powiatzyrardowski powiatzywiecki poznan + prfrawamaz proszowice prudnik pruszczgdanski @@ -3058,6 +3289,7 @@ rabkazdroj raciaz raciborz + radkowklodzki radom radomsko radomyslwielki @@ -3065,12 +3297,17 @@ radziejow radzionkow radzynpodlaski + rakoniewice rawamazowiecka rawicz reda + rejowiecpowiatchelmski + rogowopowiatrypinski + rogowopowiatzninski rogozno ropczyce rudaslaska + rudnikpowiatraciborski rumia rybnik rychwal @@ -3079,6 +3316,7 @@ rypin rzeszow rzeszowprojekt + rzgowpowiatkoninski sandomierz sanok sedziszowmalopolski @@ -3100,14 +3338,19 @@ sokolowpodlaski sopot sosnowiec + spmajkowskarzysko + spteodory srem srodaslaska srodawielkopolska starachowice stargard starogardgdanski + starostwokrosnienskie starysacz staszow + stezycapowiatrycki + stowarzyszenieintegracja stronieslaskie strzyzow sulejowek @@ -3115,9 +3358,12 @@ sulmierzyce swarzedz swidnica + swidnicapowiatswidnicki + swidnicapowiatzielonogorski swidnik swidwin swieradowzdroj + swietajnopowiatszczycienski swietochlowice swinoujscie sycow @@ -3129,6 +3375,7 @@ szprotawa sztum szubin + szydlowopowiatpilski tarnobrzeg tarnow tarnowskiegory @@ -3145,6 +3392,7 @@ turawa tuszyn tychy + uggolcza ujazd ustka ustron @@ -3154,6 +3402,20 @@ walcz warminskomazurskie warszawa + warszawabemowo + warszawabialoleka + warszawabielany + warszawamokotow + warszawapragapoludnie + warszawasrodmiescie + warszawatargowek + warszawaursus + warszawaursynow + warszawawawer + warszawawesola + warszawawlochy + warszawawola + warszawazoliborz wasosz wegrow wejherowo @@ -3161,6 +3423,10 @@ wieliczka wielkopolskie wielun + wierzbicapowiatchelmski + wierzbicapowiatradomski + wilkowpowiatnamyslowski + wisniowapowiatmyslenicki wladyslawowo wloclawek wlodawa @@ -3177,20 +3443,30 @@ zagan zakliczyn zakopane + zakrzewopowiataleksandrowski zambrow zamosc zary zawidow zdunskawola zduny + zdzwarszawa zelechow + zespolszkolppckumarszew zgierz zgorzelec zielonagora zielonka + zkso1katowice zlotoryja zlotow zory + zs2lubin + zsksieradz + zskzkwidzyn + zskzsochaczew + zspstarekozle + zstpowiatopoczynski zwolen zyrardow From f455064b9d4e2b219033b2500e02dedd08955040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sat, 2 Mar 2024 21:18:02 +0100 Subject: [PATCH 425/545] Version 2.5.0 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 12 ++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f01b9917c..6f63715ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 148 - versionName "2.4.2" + versionCode 149 + versionName "2.5.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,8 +164,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.99d - updatePriority = 2 + userFraction = 0.20d + updatePriority = 1 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.4.2-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 ef6308b6c..98c48e15d 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,11 @@ -Wersja 2.4.2 +Wersja 2.5.0 -- naprawiliśmy crash przy przełączaniu uczniów, motywów i języków -- naprawiliśmy crash przy dodawaniu dodatkowych lekcji -- naprawiliśmy obsługę błędów widżetach +— dodaliśmy wyświetlanie ogłoszeń +— dodaliśmy opcję przywracania wiadomości z kosza +— dodaliśmy opcję wyciszania nadawców wiadomości +— naprawiliśmy opcjonalne liczenie średniej arytmetycznej, kiedy brak ocen z wagą w drugim semestrze +— usprawniliśmy ładowanie frekwencji i planu lekcji +— naprawiliśmy usprawiedliwianie nieobecności i autoryzację u użytkowników eduOne +— zmieniliśmy komunikat o zmienionym haśle Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 47d8513a7766da92013943a74cdecba65c202956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 3 Mar 2024 10:35:17 +0100 Subject: [PATCH 426/545] New Crowdin updates (#2464) --- app/src/main/res/values-cs/strings.xml | 4 ++-- app/src/main/res/values-sk/strings.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c3c691c7f..58c1d7d3f 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -852,8 +852,8 @@ 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 - VULCAN\'s website requires verification - Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it + Webová stránka deníku VULCAN vyžaduje ověření + Proč se mi to zobrazuje?\nWebová stránka deníku, ze které Wulkanowy stahuje data, zobrazuje stejnou obrazovku jako výše, takže Wulkanowy ji musí také zobrazit, aby bylo možné získávat data z této stránky. Nedá se to obejít Úspěšně ověřeno Žádné internetové připojení diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 829475d60..0f5215665 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -852,8 +852,8 @@ 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ť - VULCAN\'s website requires verification - Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it + Webová stránka denníka VULCAN vyžaduje overenie + Prečo sa mi to zobrazuje?\nWebová stránka denníka, z ktorej Wulkanowy sťahuje dáta, zobrazuje rovnakú obrazovku ako vyššie, takže Wulkanowy ju musí tiež zobraziť, aby bolo možné získavať dáta z tejto stránky. Nedá sa to obísť Úspešne overené Žiadne internetové pripojenie From 0a1f7270b4009244cf8d7e346594ae719c7448db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 3 Mar 2024 11:15:11 +0100 Subject: [PATCH 427/545] Version 2.5.1 --- app/build.gradle | 8 ++++---- .../ui/modules/message/preview/MessagePreviewFragment.kt | 2 +- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6f63715ba..31de5104a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 149 - versionName "2.5.0" + versionCode 150 + versionName "2.5.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,7 +164,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.20d + userFraction = 0.50d updatePriority = 1 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.0' + implementation 'io.github.wulkanowy:sdk:2.5.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 75778bac5..ebdb96a40 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 @@ -234,7 +234,7 @@ class MessagePreviewFragment : } override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments) + outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments?.message) super.onSaveInstanceState(outState) } 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 98c48e15d..2a57977f1 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.5.0 +Wersja 2.5.1 — dodaliśmy wyświetlanie ogłoszeń — dodaliśmy opcję przywracania wiadomości z kosza From 8f5a210ec752d353328a187b3ebb6541d5e3ec2d Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:36:43 +0100 Subject: [PATCH 428/545] Add attendance calculator (#1597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Mikołaj Pich Co-authored-by: Faierbel --- .../java/io/github/wulkanowy/data/Resource.kt | 100 ++++++++++++- .../enums/AttendanceCalculatorSortingMode.kt | 13 ++ .../wulkanowy/data/pojos/AttendanceData.kt | 14 ++ .../AttendanceSummaryRepository.kt | 2 +- .../repositories/PreferencesRepository.kt | 13 ++ .../GetAttendanceCalculatorDataUseCase.kt | 103 +++++++++++++ .../modules/attendance/AttendanceFragment.kt | 6 + .../modules/attendance/AttendancePresenter.kt | 29 +++- .../ui/modules/attendance/AttendanceView.kt | 2 + .../calculator/AttendanceCalculatorAdapter.kt | 63 ++++++++ .../AttendanceCalculatorFragment.kt | 105 +++++++++++++ .../AttendanceCalculatorPresenter.kt | 84 +++++++++++ .../calculator/AttendanceCalculatorView.kt | 29 ++++ .../settings/appearance/AppearanceFragment.kt | 10 ++ .../wulkanowy/utils/AttendanceExtension.kt | 12 +- .../ic_menu_attendance_calculator.xml | 5 + .../layout/fragment_attendance_calculator.xml | 103 +++++++++++++ .../item_attendance_calculator_header.xml | 111 ++++++++++++++ .../res/layout/pref_target_attendance.xml | 140 ++++++++++++++++++ .../main/res/menu/action_menu_attendance.xml | 7 + app/src/main/res/values-pl/strings.xml | 3 + .../main/res/values/preferences_defaults.xml | 2 + app/src/main/res/values/preferences_keys.xml | 2 + .../main/res/values/preferences_values.xml | 11 ++ app/src/main/res/values/strings.xml | 7 + .../res/xml/scheme_preferences_appearance.xml | 19 +++ 26 files changed, 981 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt create mode 100644 app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt create mode 100644 app/src/main/res/drawable/ic_menu_attendance_calculator.xml create mode 100644 app/src/main/res/layout/fragment_attendance_calculator.xml create mode 100644 app/src/main/res/layout/item_attendance_calculator_header.xml create mode 100644 app/src/main/res/layout/pref_target_attendance.xml 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 108b0d58e..c698c42d5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -1,11 +1,16 @@ package io.github.wulkanowy.data +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -14,8 +19,10 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds -sealed class Resource { +sealed class Resource { open class Loading : Resource() @@ -64,6 +71,19 @@ fun Resource.mapData(block: (T) -> U) = when (this) { is Resource.Error -> Resource.Error(this.error) } +inline fun Flow>.combineWithResourceData( + flow: Flow, + crossinline block: suspend (T1, T2) -> R +): Flow> = + combine(flow) { resource, inject -> + when (resource) { + is Resource.Success -> Resource.Success(block(resource.data, inject)) + is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(resource.error) + } + } + fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach { val description = when (it) { is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" @@ -74,8 +94,29 @@ fun Flow>.logResourceStatus(name: String, showData: Boolean = fa Timber.i("$name: $description") } -fun Flow>.mapResourceData(block: (T) -> U) = map { - it.mapData(block) +fun Flow>.mapResourceData(block: suspend (T) -> U) = map { + when (it) { + is Resource.Success -> Resource.Success(block(it.data)) + is Resource.Intermediate -> Resource.Intermediate(block(it.data)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(it.error) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +fun Flow>.flatMapResourceData( + inheritIntermediate: Boolean = true, block: suspend (T) -> Flow> +) = flatMapLatest { + when (it) { + is Resource.Success -> block(it.data) + is Resource.Intermediate -> block(it.data).map { newRes -> + if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data) + else newRes + } + + is Resource.Loading -> flowOf(Resource.Loading()) + is Resource.Error -> flowOf(Resource.Error(it.error)) + } } fun Flow>.onResourceData(block: suspend (T) -> Unit) = onEach { @@ -105,13 +146,13 @@ fun Flow>.onResourceSuccess(block: suspend (T) -> Unit) = onEach } } -fun Flow>.onResourceError(block: (Throwable) -> Unit) = onEach { +fun Flow>.onResourceError(block: suspend (Throwable) -> Unit) = onEach { if (it is Resource.Error) { block(it.error) } } -fun Flow>.onResourceNotLoading(block: () -> Unit) = onEach { +fun Flow>.onResourceNotLoading(block: suspend () -> Unit) = onEach { if (it !is Resource.Loading) { block() } @@ -121,6 +162,55 @@ suspend fun Flow>.toFirstResult() = filter { it !is Resource.Loa suspend fun Flow>.waitForResult() = takeWhile { it is Resource.Loading }.collect() +// Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired, +// use `debounceIntermediates` to alleviate this behavior. +inline fun combineResourceFlows( + flows: Iterable>>, +): Flow>> = combine(flows) { items -> + var isIntermediate = false + val data = mutableListOf() + for (item in items) { + when (item) { + is Resource.Success -> data.add(item.data) + is Resource.Intermediate -> { + isIntermediate = true + data.add(item.data) + } + + is Resource.Loading -> return@combine Resource.Loading() + is Resource.Error -> continue + } + } + if (data.isEmpty()) { + // All items have to be errors for this to happen, so just return the first one. + // mapData is functionally useless and exists only to satisfy the type checker + items.first().mapData { listOf(it) } + } else if (isIntermediate) { + Resource.Intermediate(data) + } else { + Resource.Success(data) + } +} + +@OptIn(FlowPreview::class) +fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = flow { + var wasIntermediate = false + + emitAll(this@debounceIntermediates.debounce { + if (it is Resource.Intermediate) { + if (!wasIntermediate) { + wasIntermediate = true + Duration.ZERO + } else { + timeout + } + } else { + wasIntermediate = false + Duration.ZERO + } + }) +} + inline fun networkBoundResource( mutex: Mutex = Mutex(), showSavedOnLoading: Boolean = true, diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt new file mode 100644 index 000000000..77dd5fc4b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/AttendanceCalculatorSortingMode.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.enums + +enum class AttendanceCalculatorSortingMode(private val value: String) { + ALPHABETIC("alphabetic"), + ATTENDANCE("attendance_percentage"), + LESSON_BALANCE("lesson_balance"); + + companion object { + fun getByValue(value: String) = + AttendanceCalculatorSortingMode.values() + .find { it.value == value } ?: ALPHABETIC + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt new file mode 100644 index 000000000..5810363c6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/AttendanceData.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.pojos + +data class AttendanceData( + val subjectName: String, + val lessonBalance: Int, + val presences: Int, + val absences: Int, +) { + val total: Int + get() = presences + absences + + val presencePercentage: Double + get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index c6cfc2f6b..1129598ac 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -1,12 +1,12 @@ package io.github.wulkanowy.data.repositories import androidx.room.withTransaction +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey 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 64e60a60b..4735293c0 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 @@ -10,6 +10,7 @@ import com.fredporciuncula.flow.preferences.Serializer import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.AppTheme +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeSortingMode @@ -41,6 +42,18 @@ class PreferencesRepository @Inject constructor( R.bool.pref_default_attendance_present ) + val targetAttendanceFlow: Flow + get() = flowSharedPref.getInt( + context.getString(R.string.pref_key_attendance_target), + context.resources.getInteger(R.integer.pref_default_attendance_target) + ).asFlow() + + val attendanceCalculatorSortingModeFlow: Flow + get() = flowSharedPref.getString( + context.getString(R.string.pref_key_attendance_calculator_sorting_mode), + context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode) + ).asFlow().map(AttendanceCalculatorSortingMode::getByValue) + private val gradeAverageModePref: Preference get() = getObjectFlow( R.string.pref_key_grade_average_mode, diff --git a/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt new file mode 100644 index 000000000..ea68050d5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt @@ -0,0 +1,103 @@ +package io.github.wulkanowy.domain.attendance + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode +import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode.* +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SubjectRepository +import io.github.wulkanowy.utils.allAbsences +import io.github.wulkanowy.utils.allPresences +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import kotlin.math.ceil +import kotlin.math.floor + +class GetAttendanceCalculatorDataUseCase @Inject constructor( + private val subjectRepository: SubjectRepository, + private val attendanceSummaryRepository: AttendanceSummaryRepository, + private val preferencesRepository: PreferencesRepository, +) { + + operator fun invoke( + student: Student, + semester: Semester, + forceRefresh: Boolean, + ): Flow>> = + subjectRepository.getSubjects(student, semester, forceRefresh) + .mapResourceData { subjects -> subjects.sortedBy(Subject::name) } + .combineWithResourceData(preferencesRepository.targetAttendanceFlow, ::Pair) + .flatMapResourceData { (subjects, targetFreq) -> + combineResourceFlows(subjects.map { subject -> + attendanceSummaryRepository.getAttendanceSummary( + student = student, + semester = semester, + subjectId = subject.realId, + forceRefresh = forceRefresh + ).mapResourceData { summaries -> + summaries.toAttendanceData(subject.name, targetFreq) + } + }) + // Every individual combined flow causes separate network requests to update data. + // When there is N child flows, they can cause up to N-1 items to be emitted. Since all + // requests are usually completed in less than 5s, there is no need to emit multiple + // intermediates that will be visible for barely any time. + .debounceIntermediates() + } + .combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List::sortedBy) +} + +private fun List.toAttendanceData(subjectName: String, targetFreq: Int): AttendanceData { + val presences = sumOf { it.allPresences } + val absences = sumOf { it.allAbsences } + return AttendanceData( + subjectName = subjectName, + lessonBalance = calcLessonBalance( + targetFreq.toDouble() / 100, presences, absences + ), + presences = presences, + absences = absences, + ) +} + +private fun calcLessonBalance(targetFreq: Double, presences: Int, absences: Int): Int { + val total = presences + absences + // The `+ 1` is to avoid false positives in close cases. Eg.: + // target frequency 99%, 1 presence. Without the `+ 1` this would be reported shown as + // a positive balance of +1, however that is not actually true as skipping one class + // would make it so that the balance would actually be negative (-98). The `+ 1` + // fixes this and makes sure that in situations like these, it's not reporting incorrect + // balances + return when { + presences / (total + 1f) >= targetFreq -> calcMissingAbsences( + targetFreq, absences, presences + ) + presences / (total + 0f) < targetFreq -> -calcMissingPresences( + targetFreq, absences, presences + ) + else -> 0 + } +} + +private fun calcMissingPresences(targetFreq: Double, absences: Int, presences: Int) = + calcMinRequiredPresencesFor(targetFreq, absences) - presences + +private fun calcMinRequiredPresencesFor(targetFreq: Double, absences: Int) = + ceil((targetFreq / (1 - targetFreq)) * absences).toInt() + +private fun calcMissingAbsences(targetFreq: Double, absences: Int, presences: Int) = + calcMinRequiredAbsencesFor(targetFreq, presences) - absences + +private fun calcMinRequiredAbsencesFor(targetFreq: Double, presences: Int) = + floor((presences * (1 - targetFreq)) / targetFreq).toInt() + +private fun List.sortedBy(mode: AttendanceCalculatorSortingMode) = when (mode) { + ALPHABETIC -> sortedBy(AttendanceData::subjectName) + ATTENDANCE -> sortedByDescending(AttendanceData::presencePercentage) + LESSON_BALANCE -> sortedBy(AttendanceData::lessonBalance) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 6e842b4d7..07649e436 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -14,6 +14,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogExcuseBinding import io.github.wulkanowy.databinding.FragmentAttendanceBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.attendance.calculator.AttendanceCalculatorFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView @@ -134,6 +135,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected() + else if (item.itemId == R.id.attendanceMenuCalculator) presenter.onCalculatorSwitchSelected() else false } @@ -253,6 +255,10 @@ class AttendanceFragment : BaseFragment(R.layout.frag (activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance()) } + override fun openCalculatorView() { + (activity as? MainActivity)?.pushView(AttendanceCalculatorFragment.newInstance()) + } + override fun startActionMode() { actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 82fe69cb7..586a41ad0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,16 +1,36 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.flatResourceFlow +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.mapResourceData +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceIntermediate +import io.github.wulkanowy.data.onResourceLoading +import io.github.wulkanowy.data.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.AttendanceRepository 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.resourceFlow 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.isExcusableOrNotExcused +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.previousOrSameSchoolDay +import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -195,6 +215,11 @@ class AttendancePresenter @Inject constructor( return true } + fun onCalculatorSwitchSelected(): Boolean { + view?.openCalculatorView() + return true + } + private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading attendance data started") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index 2629c217e..f51ce7c7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -56,6 +56,8 @@ interface AttendanceView : BaseView { fun openSummaryView() + fun openCalculatorView() + fun startSendMessageIntent(date: LocalDate, numbers: String, reason: String) fun startActionMode() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt new file mode 100644 index 000000000..73c08fd32 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt @@ -0,0 +1,63 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.databinding.ItemAttendanceCalculatorHeaderBinding +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +class AttendanceCalculatorAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ) = ViewHolder( + ItemAttendanceCalculatorHeaderBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(parent: ViewHolder, position: Int) { + with(parent.binding) { + val item = items[position] + attendanceCalculatorPercentage.text = "${item.presencePercentage.roundToInt()}" + + if (item.lessonBalance > 0) { + attendanceCalculatorSummaryBalance.text = root.context.getString( + R.string.attendance_calculator_summary_balance_positive, + item.lessonBalance + ) + } else if (item.lessonBalance < 0) { + attendanceCalculatorSummaryBalance.text = root.context.getString( + R.string.attendance_calculator_summary_balance_negative, + abs(item.lessonBalance) + ) + } else { + attendanceCalculatorSummaryBalance.text = root.context.getString( + R.string.attendance_calculator_summary_balance_neutral, + ) + } + attendanceCalculatorWarning.isVisible = item.lessonBalance < 0 + attendanceCalculatorTitle.text = item.subjectName + attendanceCalculatorSummaryValues.text = root.context.getString( + R.string.attendance_calculator_summary_values, + item.presences, + item.total + ) + } + } + + class ViewHolder(val binding: ItemAttendanceCalculatorHeaderBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt new file mode 100644 index 000000000..2d5667015 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt @@ -0,0 +1,105 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.getThemeAttrColor +import javax.inject.Inject + +@AndroidEntryPoint +class AttendanceCalculatorFragment : + BaseFragment(R.layout.fragment_attendance_calculator), + AttendanceCalculatorView, MainView.TitledView { + + @Inject + lateinit var presenter: AttendanceCalculatorPresenter + + @Inject + lateinit var attendanceCalculatorAdapter: AttendanceCalculatorAdapter + + override val titleStringId get() = R.string.attendance_title + + companion object { + fun newInstance() = AttendanceCalculatorFragment() + } + + override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentAttendanceCalculatorBinding.bind(view) + messageContainer = binding.attendanceCalculatorRecycler + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.attendanceCalculatorRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = attendanceCalculatorAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) + attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() } + attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun updateData(data: List) { + with(attendanceCalculatorAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearView() { + with(attendanceCalculatorAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showEmpty(show: Boolean) { + binding.attendanceCalculatorEmpty.isVisible = show + } + + override fun showErrorView(show: Boolean) { + binding.attendanceCalculatorError.isVisible = show + } + + override fun setErrorDetails(message: String) { + binding.attendanceCalculatorErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.attendanceCalculatorProgress.isVisible = show + } + + override fun enableSwipe(enable: Boolean) { + binding.attendanceCalculatorSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.attendanceCalculatorRecycler.isVisible = show + } + + override fun showRefresh(show: Boolean) { + binding.attendanceCalculatorSwipe.isRefreshing = show + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt new file mode 100644 index 000000000..d292e5650 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt @@ -0,0 +1,84 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import timber.log.Timber +import javax.inject.Inject + +class AttendanceCalculatorPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val getAttendanceCalculatorData: GetAttendanceCalculatorDataUseCase, +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: AttendanceCalculatorView) { + super.onAttachView(view) + view.initView() + Timber.i("Attendance calculator view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the attendance calculator") + loadData(forceRefresh = true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun loadData(forceRefresh: Boolean = false) { + flatResourceFlow { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + getAttendanceCalculatorData(student, semester, forceRefresh) + } + .logResourceStatus("load attendance calculator") + .onResourceData { + view?.run { + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) + } + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt new file mode 100644 index 000000000..94e661212 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.ui.modules.attendance.calculator + +import io.github.wulkanowy.data.pojos.AttendanceData +import io.github.wulkanowy.ui.base.BaseView + +interface AttendanceCalculatorView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun showRefresh(show: Boolean) + + fun showContent(show: Boolean) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun updateData(data: List) + + fun clearView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 3d0c8052b..ba234aae2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -4,6 +4,7 @@ import android.content.SharedPreferences import android.os.Bundle import android.view.View import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SeekBarPreference import com.yariksoffice.lingver.Lingver import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -36,6 +37,15 @@ class AppearanceFragment : PreferenceFragmentCompat(), override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.scheme_preferences_appearance, rootKey) + val attendanceTargetPref = + findPreference(requireContext().getString(R.string.pref_key_attendance_target))!! + attendanceTargetPref.setOnPreferenceChangeListener { _, newValueObj -> + val newValue = (((newValueObj as Int).toDouble() + 2.5) / 5).toInt() * 5 + attendanceTargetPref.value = + newValue.coerceIn(attendanceTargetPref.min, attendanceTargetPref.max) + + false + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt index 397c95953..3cac0b48e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt @@ -10,19 +10,19 @@ import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory * (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf) */ -private inline val AttendanceSummary.allPresences: Double - get() = presence.toDouble() + absenceForSchoolReasons + lateness + latenessExcused +inline val AttendanceSummary.allPresences: Int + get() = presence + absenceForSchoolReasons + lateness + latenessExcused -private inline val AttendanceSummary.allAbsences: Double - get() = absence.toDouble() + absenceExcused +inline val AttendanceSummary.allAbsences: Int + get() = absence + absenceExcused inline val Attendance.isExcusableOrNotExcused: Boolean get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null -fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences) +fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences.toDouble(), allAbsences.toDouble()) fun List.calculatePercentage(): Double { - return calculatePercentage(sumOf { it.allPresences }, sumOf { it.allAbsences }) + return calculatePercentage(sumOf { it.allPresences.toDouble() }, sumOf { it.allAbsences.toDouble() }) } private fun calculatePercentage(presence: Double, absence: Double): Double { diff --git a/app/src/main/res/drawable/ic_menu_attendance_calculator.xml b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml new file mode 100644 index 000000000..8a7d209a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_attendance_calculator.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_attendance_calculator.xml b/app/src/main/res/layout/fragment_attendance_calculator.xml new file mode 100644 index 000000000..346c6aecd --- /dev/null +++ b/app/src/main/res/layout/fragment_attendance_calculator.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attendance_calculator_header.xml b/app/src/main/res/layout/item_attendance_calculator_header.xml new file mode 100644 index 000000000..debc79979 --- /dev/null +++ b/app/src/main/res/layout/item_attendance_calculator_header.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/pref_target_attendance.xml b/app/src/main/res/layout/pref_target_attendance.xml new file mode 100644 index 000000000..558b0d36f --- /dev/null +++ b/app/src/main/res/layout/pref_target_attendance.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/action_menu_attendance.xml b/app/src/main/res/menu/action_menu_attendance.xml index bb20c8ec2..5c59d2391 100644 --- a/app/src/main/res/menu/action_menu_attendance.xml +++ b/app/src/main/res/menu/action_menu_attendance.xml @@ -1,6 +1,13 @@ + Wyłącz wyciszenie Wyciszyleś tego użytkownika Wyłączyłeś wyciszenie tego użytkownika + + Docelowa frekwencja (w %) + Kalkulator frekwencji diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 8e6fc7d66..109418893 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -2,6 +2,8 @@ 0 true + 50 + alphabetic only_one_semester false one diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 74af9262c..e95c59405 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -2,6 +2,8 @@ default_menu_index attendance_present + attendance_target + attendance_calculator_sorting_mode app_theme dashboard_tiles grade_color_scheme diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index f56707c89..2475e4914 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -79,6 +79,17 @@ 0.75 + + Alphabetically + By attendance percentage + By lesson balance + + + alphabetic + attendance_percentage + lesson_balance + + Alphabetically By date diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2775365d5..ae6d91408 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -258,6 +258,11 @@ Attendance summary + Attendance calculator + %1$d over target + right on target + %1$d under target + %1$d/%2$d presences Absent for school reasons Excused absence Unexcused absence @@ -715,6 +720,8 @@ Calculated average options Force average calculation by app Show presence + Attendance target + Attendance calculator sorting Theme Grades expanding Show groups next to subjects diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index 9c02a4910..46a0e6a92 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -85,6 +85,25 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_attendance_present" app:title="@string/pref_view_present" /> + + Date: Fri, 8 Mar 2024 20:57:26 +0100 Subject: [PATCH 429/545] Update gradle wrapper to 8.6 (#2468) --- .gitignore | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 980085e38..c014204d0 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ captures/ .idea/discord.xml .idea/migrations.xml .idea/androidTestResultsUserPreferences.xml +.idea/copilot # Keystore files *.jks diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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

l-wUqma}v2c(1Oi^~*7`Q^d3B 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 literal 63721 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"gpYvgVMVsULOY3=lDL#2r|Jj~bidD9% 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 z Date: Fri, 8 Mar 2024 20:13:58 +0000 Subject: [PATCH 430/545] Bump kotlin_version from 1.9.22 to 1.9.23 (#2466) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ec19ee49a..f245e71b1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.9.22' + kotlin_version = '1.9.23' about_libraries = '11.1.0' hilt_version = '2.51' } @@ -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.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.19" classpath 'com.android.tools.build:gradle:8.2.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.1' From a71ef4a4b24a7c6dbcfca28415debd727757b7bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:14:33 +0000 Subject: [PATCH 431/545] Bump com.google.firebase:firebase-bom from 32.7.3 to 32.7.4 (#2467) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 31de5104a..ef3476611 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.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.7.3') + playImplementation platform('com.google.firebase:firebase-bom:32.7.4') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From a7c2009e4971891f8e756ccca504cddee727c689 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:16:03 +0000 Subject: [PATCH 432/545] Bump com.google.android.gms:play-services-ads from 22.6.0 to 23.0.0 (#2469) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ef3476611..3afd068be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -258,7 +258,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-config' - playImplementation 'com.google.android.gms:play-services-ads:22.6.0' + playImplementation 'com.google.android.gms:play-services-ads:23.0.0' playImplementation "com.google.android.play:integrity:1.3.0" playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:review-ktx:2.0.1' From 5dd5697f650ac6e2fd44103692bfe499e5c64437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 9 Mar 2024 10:03:36 +0100 Subject: [PATCH 433/545] Remove firebase disable flag (#2471) --- .circleci/config.yml | 2 +- .github/workflows/deploy-store.yml | 2 +- .github/workflows/deploy-test.yml | 5 +- .gitignore | 13 ++-- .travis.yml | 2 +- app/build.gradle | 6 +- app/{src/debug => }/google-services.json | 0 app/src/debug/agconnect-services.json | 92 ------------------------ app/src/main/AndroidManifest.xml | 24 ------- app/src/release/google-services.json | 42 ----------- 10 files changed, 14 insertions(+), 174 deletions(-) rename app/{src/debug => }/google-services.json (100%) delete mode 100644 app/src/debug/agconnect-services.json delete mode 100644 app/src/release/google-services.json diff --git a/.circleci/config.yml b/.circleci/config.yml index ce2922ba3..2cb2e1473 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,7 +162,7 @@ jobs: openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks - run: name: Publish release - command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex + command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex workflows: version: 2 diff --git a/.github/workflows/deploy-store.yml b/.github/workflows/deploy-store.yml index e8237a381..0195f3e56 100644 --- a/.github/workflows/deploy-store.yml +++ b/.github/workflows/deploy-store.yml @@ -40,7 +40,7 @@ jobs: SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }} DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }} SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }} - run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace; + run: ./gradlew publishPlayReleaseApps --stacktrace; deploy-app-gallery: name: AppGallery diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index c4f55e6af..42c1f8e7a 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -36,8 +36,7 @@ jobs: - name: Prepare build configuration run: | sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle - sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/google-services.json - sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/agconnect-services.json + sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/google-services.json sed -i -e '/versionNameSuffix/d' app/build.gradle - name: Add signing config run: | @@ -131,7 +130,7 @@ jobs: BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }} BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }} BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }} - run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace + run: ./gradlew assemblePlayDebug --stacktrace - name: Upload apk to github artifacts uses: actions/upload-artifact@v3 with: diff --git a/.gitignore b/.gitignore index c014204d0..ee435baa3 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,8 @@ captures/ .idea/migrations.xml .idea/androidTestResultsUserPreferences.xml .idea/copilot +.idea/deploymentTargetDropDown.xml +.idea/kotlinc.xml # Keystore files *.jks @@ -114,12 +116,13 @@ Thumbs.db *.ear ### AndroidStudio Patch ### - !/gradle/wrapper/gradle-wrapper.jar .idea/jarRepositories.xml +### Services config files +agconnect-services.json +agconnect-credentials.json +google-services.json +!app/google-services.json + -app/src/release/agconnect-services.json -app/src/release/agconnect-credentials.json -.idea/deploymentTargetDropDown.xml -.idea/kotlinc.xml diff --git a/.travis.yml b/.travis.yml index 04db3a616..e0b0be978 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,7 +61,7 @@ script: gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; - ./gradlew publishPlayRelease -PenableFirebase --stacktrace; + ./gradlew publishPlayRelease --stacktrace; fi after_success: diff --git a/app/build.gradle b/app/build.gradle index 3afd068be..d1c67ab0a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,10 +32,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" - manifestPlaceholders = [ - firebase_enabled: project.hasProperty("enableFirebase"), - admob_project_id: "" - ] + manifestPlaceholders = [admob_project_id: ""] buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null" @@ -76,7 +73,6 @@ android { resValue "string", "app_name", "Wulkanowy DEV" applicationIdSuffix ".dev" versionNameSuffix "-dev" - ext.enableCrashlytics = project.hasProperty("enableFirebase") buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' } diff --git a/app/src/debug/google-services.json b/app/google-services.json similarity index 100% rename from app/src/debug/google-services.json rename to app/google-services.json diff --git a/app/src/debug/agconnect-services.json b/app/src/debug/agconnect-services.json deleted file mode 100644 index 52426f54e..000000000 --- a/app/src/debug/agconnect-services.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "agcgw": { - "backurl": "connect-dre.hispace.hicloud.com", - "url": "connect-dre.dbankcloud.cn", - "websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com", - "websocketurl": "connect-ws-dre.hispace.dbankcloud.cn" - }, - "agcgw_all": { - "CN": "connect-drcn.dbankcloud.cn", - "CN_back": "connect-drcn.hispace.hicloud.com", - "DE": "connect-dre.dbankcloud.cn", - "DE_back": "connect-dre.hispace.hicloud.com", - "RU": "connect-drru.hispace.dbankcloud.ru", - "RU_back": "connect-drru.hispace.dbankcloud.cn", - "SG": "connect-dra.dbankcloud.cn", - "SG_back": "connect-dra.hispace.hicloud.com" - }, - "websocketgw_all": { - "CN": "connect-ws-drcn.hispace.dbankcloud.cn", - "CN_back": "connect-ws-drcn.hispace.dbankcloud.com", - "DE": "connect-ws-dre.hispace.dbankcloud.cn", - "DE_back": "connect-ws-dre.hispace.dbankcloud.com", - "RU": "connect-ws-drru.hispace.dbankcloud.ru", - "RU_back": "connect-ws-drru.hispace.dbankcloud.cn", - "SG": "connect-ws-dra.hispace.dbankcloud.cn", - "SG_back": "connect-ws-dra.hispace.dbankcloud.com" - }, - "client": { - "cp_id": "890048000024105546", - "product_id": "736430079244736562", - "client_id": "514530959291319360", - "client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B", - "project_id": "736430079244736562", - "app_id": "106552551", - "api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS", - "package_name": "io.github.wulkanowy.dev" - }, - "oauth_client": { - "client_id": "106552551", - "client_type": 1 - }, - "app_info": { - "app_id": "106552551", - "package_name": "io.github.wulkanowy.dev" - }, - "service": { - "analytics": { - "collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", - "collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", - "collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", - "collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", - "collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", - "resource_id": "p1", - "channel_id": "" - }, - "search":{ - "url":"https://search-dre.cloud.huawei.com" - }, - "cloudstorage": { - "storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia", - "storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru", - "storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru", - "storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu", - "storage_url_de": "https://ops-dre.agcstorage.link", - "storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn", - "storage_url_sg": "https://ops-dra.agcstorage.link", - "storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn", - "storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn" - }, - "ml": { - "mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn" - } - }, - "region": "DE", - "configuration_version": "3.0", - "appInfos": [ - { - "package_name": "io.github.wulkanowy.dev", - "client": { - "app_id": "106552551" - }, - "app_info": { - "package_name": "io.github.wulkanowy.dev", - "app_id": "106552551" - }, - "oauth_client": { - "client_type": 1, - "client_id": "106552551" - } - } - ] -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f43dfdd2c..4e617c931 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -155,33 +155,9 @@ android:resource="@xml/provider_paths" /> - - - - - - - - - diff --git a/app/src/release/google-services.json b/app/src/release/google-services.json deleted file mode 100644 index ebd157e1d..000000000 --- a/app/src/release/google-services.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "project_info": { - "project_number": "", - "firebase_url": "", - "project_id": "", - "storage_bucket": "" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1", - "android_client_info": { - "package_name": "io.github.wulkanowy" - } - }, - "oauth_client": [ - { - "client_id": "", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} From 4ef9fb1f2866247007f0181726814be71ac820cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 9 Mar 2024 10:05:12 +0100 Subject: [PATCH 434/545] Update preferences strings (#2472) --- app/src/main/res/values/preferences_values.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index 2475e4914..920482020 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -1,5 +1,8 @@ + + Alphabetically + @string/dashboard_title @string/grade_title @@ -80,9 +83,9 @@ - Alphabetically + @string/general_alphabetically By attendance percentage - By lesson balance + By subject attendance balance alphabetic @@ -91,7 +94,7 @@ - Alphabetically + @string/general_alphabetically By date By average From c72cc39920226bca53c0114fdd9e69673bde90a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 9 Mar 2024 21:01:58 +0100 Subject: [PATCH 435/545] Separate strings from array to avoid duplications (#2473) --- .../main/res/values-cs/preferences_values.xml | 5 -- .../res/values-da-rDK/preferences_values.xml | 70 ------------------- .../main/res/values-de/preferences_values.xml | 5 -- .../res/values-es-rES/preferences_values.xml | 70 ------------------- .../res/values-it-rIT/preferences_values.xml | 70 ------------------- .../main/res/values-pl/preferences_values.xml | 5 -- .../main/res/values-ru/preferences_values.xml | 5 -- .../main/res/values-sk/preferences_values.xml | 5 -- .../main/res/values-uk/preferences_values.xml | 5 -- .../main/res/values/preferences_values.xml | 22 +++--- 10 files changed, 13 insertions(+), 249 deletions(-) delete mode 100644 app/src/main/res/values-da-rDK/preferences_values.xml delete mode 100644 app/src/main/res/values-es-rES/preferences_values.xml delete mode 100644 app/src/main/res/values-it-rIT/preferences_values.xml diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index 1590c47ab..5d6c5e8c5 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - Abecedně - Podle data - Podle průměru - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values-da-rDK/preferences_values.xml b/app/src/main/res/values-da-rDK/preferences_values.xml deleted file mode 100644 index 5aff12dec..000000000 --- a/app/src/main/res/values-da-rDK/preferences_values.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 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 - - - Don\'t show - Only between lessons - Before and between lessons - - - Lucky number - Unread messages - Attendance - Lessons - Grades - Homework - School announcements - Exams - Conferences - - diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index d1001c74b..17c19e7d1 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - Alphabetisch - Nach Datum - Nach Durchschnitt - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values-es-rES/preferences_values.xml b/app/src/main/res/values-es-rES/preferences_values.xml deleted file mode 100644 index 5aff12dec..000000000 --- a/app/src/main/res/values-es-rES/preferences_values.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 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 - - - Don\'t show - Only between lessons - Before and between lessons - - - Lucky number - Unread messages - Attendance - Lessons - Grades - Homework - School announcements - Exams - Conferences - - diff --git a/app/src/main/res/values-it-rIT/preferences_values.xml b/app/src/main/res/values-it-rIT/preferences_values.xml deleted file mode 100644 index 5aff12dec..000000000 --- a/app/src/main/res/values-it-rIT/preferences_values.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 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 - - - Don\'t show - Only between lessons - Before and between lessons - - - Lucky number - Unread messages - Attendance - Lessons - Grades - Homework - School announcements - Exams - Conferences - - diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 2f2432e98..70b812944 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - Alfabetycznie - Według daty - Według średniej - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index df3629c02..16b337a76 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - В алфавитном порядке - По дате - По средней - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml index 6cd221540..c9862751e 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - Abecedne - Podľa dátumu - Podľa priemeru - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index c02efb54a..f0cfdd122 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -31,11 +31,6 @@ 0,5 0,75 - - За алфавітом - За датою - За середньою - Dzienniczek+ Wulkanowy diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index 920482020..b588ea5e1 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -1,7 +1,11 @@ - Alphabetically + Alphabetically + By date + By average + By attendance percentage + By subject attendance balance @string/dashboard_title @@ -82,10 +86,10 @@ 0.75 - - @string/general_alphabetically - By attendance percentage - By subject attendance balance + + @string/sort_alphabetically + @string/sort_by_attendance_percentage + @string/sort_by_subject_attendance_balance alphabetic @@ -93,10 +97,10 @@ lesson_balance - - @string/general_alphabetically - By date - By average + + @string/sort_alphabetically + @string/sort_by_date + @string/sort_by_average alphabetic From 38c00ddab5815c3e430260c8c2bc6124cbd4c6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:44:59 +0100 Subject: [PATCH 436/545] Fix task description color crash (#2475) --- .gitignore | 1 + .../io/github/wulkanowy/ui/base/BaseActivity.kt | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ee435baa3..ad83ced8d 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ captures/ .idea/androidTestResultsUserPreferences.xml .idea/copilot .idea/deploymentTargetDropDown.xml +.idea/deploymentTargetSelector.xml .idea/kotlinc.xml # Keystore files diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 10735dab3..922c35365 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.base import android.app.ActivityManager +import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast @@ -45,11 +46,19 @@ abstract class BaseActivity, VB : ViewBinding> : themeManager.applyActivityTheme(this) super.onCreate(savedInstanceState) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true) + applyCustomTaskDescription() + } - @Suppress("DEPRECATION") - setTaskDescription( - ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)) - ) + @Suppress("DEPRECATION") + private fun applyCustomTaskDescription() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return + try { + val newColor = getThemeAttrColor(R.attr.colorSurface) + val taskDescription = ActivityManager.TaskDescription(null, null, newColor) + setTaskDescription(taskDescription) + } catch (e: Exception) { + Timber.e(e) + } } override fun showError(text: String, error: Throwable) { From 0e99c81eb8c58ffe62ff0460103737c615a04059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:45:15 +0100 Subject: [PATCH 437/545] Add missing onDetachView in AutDialog (#2476) --- .../java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt index fa29df473..0f7c4234e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt @@ -78,4 +78,9 @@ class AuthDialog : BaseDialogFragment(), AuthView { override fun showDescriptionWithName(name: String) { binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml() } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } } From 88def5eff8ccc2a5575e070e755c448bfb4b3277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:45:28 +0100 Subject: [PATCH 438/545] Remove savedInstance in MessagePreviewFragment (#2477) --- .../mailboxchooser/MailboxChooserDialog.kt | 1 - .../message/preview/MessagePreviewFragment.kt | 11 +++-------- .../message/preview/MessagePreviewPresenter.kt | 13 +++++++++---- .../github/wulkanowy/utils/BundleExtension.kt | 17 +++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt index 8bd84f2bf..11d3c6c12 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -47,7 +47,6 @@ class MailboxChooserDialog : BaseDialogFragment(), } - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView( 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 ebdb96a40..8e7c72765 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 @@ -82,10 +82,10 @@ class MessagePreviewFragment : get() = getString(R.string.message_not_exists) companion object { - const val MESSAGE_ID_KEY = "message_id" + private const val MESSAGE_ARG_KEY = "message" fun newInstance(message: Message) = MessagePreviewFragment().apply { - arguments = bundleOf(MESSAGE_ID_KEY to message) + arguments = bundleOf(MESSAGE_ARG_KEY to message) } } @@ -101,7 +101,7 @@ class MessagePreviewFragment : messageContainer = binding.messagePreviewContainer presenter.onAttachView( view = this, - message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY), + message = requireArguments().serializable(MESSAGE_ARG_KEY), ) } @@ -233,11 +233,6 @@ class MessagePreviewFragment : (activity as MainActivity).popView() } - override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments?.message) - super.onSaveInstanceState(outState) - } - override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() 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 9bb0d32a4..3b3b2b420 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 @@ -3,10 +3,15 @@ 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.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder +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.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository @@ -28,17 +33,17 @@ class MessagePreviewPresenter @Inject constructor( private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { - var messageWithAttachments: MessageWithAttachment? = null + private var messageWithAttachments: MessageWithAttachment? = null private lateinit var lastError: Throwable private var retryCallback: () -> Unit = {} - fun onAttachView(view: MessagePreviewView, message: Message?) { + fun onAttachView(view: MessagePreviewView, message: Message) { super.onAttachView(view) view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(requireNotNull(message)) + loadData(message) } private fun onMessageLoadRetry(message: Message) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt index d3c9f8006..b1742b4fa 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt @@ -4,30 +4,31 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.os.Parcelable +import androidx.core.os.BundleCompat import java.io.Serializable +// Even though API was introduced in 33, we use 34 as 33 is bugged in some scenarios. + inline fun Bundle.serializable(key: String): T = when { - Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!! + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java)!! else -> @Suppress("DEPRECATION") getSerializable(key) as T } inline fun Bundle.nullableSerializable(key: String): T? = when { - Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java) + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java) else -> @Suppress("DEPRECATION") getSerializable(key) as T? } @Suppress("UNCHECKED_CAST") -inline fun Bundle.parcelableArray(key: String): Array? = when { - Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java) - else -> @Suppress("DEPRECATION") getParcelableArray(key) as Array? -} +inline fun Bundle.parcelableArray(key: String): Array? = + BundleCompat.getParcelableArray(this, key, T::class.java) as Array? inline fun Intent.serializable(key: String): T = when { - Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!! + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java)!! else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T } inline fun Intent.nullableSerializable(key: String): T? = when { - Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java) + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java) else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T? } From eb6fdd900e9ba393b1fe3fd00439a0b1f2f4468a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:47:13 +0100 Subject: [PATCH 439/545] New Crowdin updates (#2470) --- .../main/res/values-cs/preferences_values.xml | 7 +++- app/src/main/res/values-cs/strings.xml | 41 +++++++++++-------- .../main/res/values-de/preferences_values.xml | 5 +++ app/src/main/res/values-de/strings.xml | 7 ++++ .../main/res/values-pl/preferences_values.xml | 5 +++ app/src/main/res/values-pl/strings.xml | 10 +++-- .../main/res/values-ru/preferences_values.xml | 5 +++ app/src/main/res/values-ru/strings.xml | 7 ++++ .../main/res/values-sk/preferences_values.xml | 7 +++- app/src/main/res/values-sk/strings.xml | 41 +++++++++++-------- .../main/res/values-uk/preferences_values.xml | 5 +++ app/src/main/res/values-uk/strings.xml | 7 ++++ 12 files changed, 108 insertions(+), 39 deletions(-) diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index 5d6c5e8c5..5e488e40c 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -1,5 +1,10 @@ + Abecedně + Podle data + Podle průměru + Podle procenta docházky + Podle rovnováhy docházky předmětu Světlý Tmavý @@ -54,7 +59,7 @@ Šťastné číslo Nepřečtené zprávy - Frekvence + Docházka Lekce Známky Domácí úkoly diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 58c1d7d3f..780d9351c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -4,7 +4,7 @@ Přihlášení Wulkanowy Známky - Frekvence + Docházka Zkoušky Plán lekce Nastavení @@ -64,7 +64,7 @@ Symbol najdete na stránce deníku v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli Variace deníku UONET+ na první přihlašovací obrazovce Vyberte žáky, kteří se mají do aplikace přihlásit Jiné možnosti - V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí frekvencí, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení + V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, dokončené lekce, informace o škole a prohlížení seznamu registrovaných zařízení Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka Kombinace nejlepších vlastností ostatních dvou režimů. Funguje rychleji než scraper a poskytuje funkce, které nejsou k dispozici v režimu Mobile API. Je to v experimentální fázi Ochrana osobních údajů @@ -264,7 +264,12 @@ Čas ukončení Čas ukončení musí být pozdější než čas zahájení - Shrnutí frekvencí + Shrnutí docházky + Kalkulačka docházky + %1$d nad cílem + přesně v cíli + %1$d pod cílem + %1$d/%2$d přítomnosti Nepřítomnost ze školních důvodů Omluvená nepřítomnost Neomluvená nepřítomnost @@ -282,22 +287,22 @@ Musíte vybrat alespoň jednu nepřítomnost! Ospravedlnit - Nové frekvence - Nové frekvence - Nové frekvence - Nové frekvence + Nová docházka + Nové docházky + Nové docházky + Nové docházky - %1$d nové frekvence - %1$d nové frekvence - %1$d nových frekvencí - %1$d nových frekvencí + %1$d nová docházka + %1$d nové docházky + %1$d nových docházek + %1$d nových docházek - %d frekvence - %d frekvence - %d frekvencí - %d frekvencí + %d docházka + %d docházky + %d docházek + %d docházek Společně @@ -731,6 +736,8 @@ Možnosti vypočítaného průměru Vynutit průměrný výpočet podle aplikace Zobrazit přítomnost + Cílová docházka + Třídění kalkulačky docházky Motiv Rozvíjení známek Zobrazit skupiny vedle předmětů @@ -797,7 +804,7 @@ Známky Domů Viditelnost dlaždic - Frekvence + Docházka Plán lekce Známky Vypočítaný průměr @@ -825,7 +832,7 @@ Nadcházející lekce Ladění Změny plánu lekcí - Nové frekvence + Nové docházky Černá Červená diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 17c19e7d1..0170acfa3 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -1,5 +1,10 @@ + Alphabetically + By date + By average + By attendance percentage + By subject attendance balance Licht Dunkel diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index daabc7d8f..7bc5aa990 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -237,6 +237,11 @@ Endzeit muss grösser sein als Startzeit Übersicht über die Schulbesuch + Attendance calculator + %1$d over target + right on target + %1$d under target + %1$d/%2$d presences Aus schulischen Gründen abwesend Entschuldigte Abwesenheit Unentschuldigtes Abwesenheit @@ -637,6 +642,8 @@ Berechnete Durchschnittsoptionen Mittelwertberechnung durch App erzwingen Anwesendheit zeigen + Attendance target + Attendance calculator sorting Thema Steigende Sorten Gruppen neben Schulfächen anzeigen diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 70b812944..4df60b51f 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -1,5 +1,10 @@ + Alfabetycznie + Według daty + Według średniej + Według procentu obecności + Według balansu frekwencji przedmiotu Jasny Ciemny diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 91a00e512..d1d603b60 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -265,6 +265,11 @@ Godzina zakończenia musi być późniejsza niż godzina rozpoczęcia Podsumowanie frekwencji + Kalkulator obecności + %1$d powyżej celu + dokładnie u celu + %1$d poniżej celu + %1$d/%2$d obecności Nieobecność z przyczyn szkolnych Nieobecność usprawiedliwiona Nieobecność nieusprawiedliwiona @@ -731,6 +736,8 @@ Opcje obliczonej średniej Wymuś obliczanie średniej przez aplikację Pokazuj obecność + Docelowa obecność + Sortowanie kalkulatora obecności Motyw Rozwijanie ocen Pokazuj grupę obok przedmiotu @@ -876,7 +883,4 @@ Wyłącz wyciszenie Wyciszyleś tego użytkownika Wyłączyłeś wyciszenie tego użytkownika - - Docelowa frekwencja (w %) - Kalkulator frekwencji diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 16b337a76..8d4bd8d7b 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -1,5 +1,10 @@ + Alphabetically + By date + By average + By attendance percentage + By subject attendance balance Светлая Тёмная diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8a5fcc40d..0e7e0e1d2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -265,6 +265,11 @@ Время окончания должно быть больше, чем время начала Итоговая посещаемость + Attendance calculator + %1$d over target + right on target + %1$d under target + %1$d/%2$d presences Отсутствие по школьным причинам Отсутствие по уважительной причине Отсутствие по неуважительной причине @@ -731,6 +736,8 @@ Параметры расчёта средних оценок Принудительно высчитать среднюю оценку через приложение Показывать присутствия + Attendance target + Attendance calculator sorting Тема Разворачивание оценок Показать группы рядом с темами diff --git a/app/src/main/res/values-sk/preferences_values.xml b/app/src/main/res/values-sk/preferences_values.xml index c9862751e..d78dd92da 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -1,5 +1,10 @@ + Abecedne + Podľa dátumu + Podľa priemeru + Podľa percenta dochádzky + Podľa rovnováhy dochádzky predmetu Svetlý Tmavý @@ -54,7 +59,7 @@ Šťastné číslo Neprečítané správy - Frekvencia + Dochádzka Lekcie Známky Domáce úlohy diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 0f5215665..9dbf72820 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -4,7 +4,7 @@ Prihlásenie Wulkanowy Známky - Frekvencia + Dochádzka Skúšky Plán lekcie Nastavenia @@ -64,7 +64,7 @@ Symbol nájdete na stránke denníka v  Uczeń→ Dostęp Mobilny → Wygeneruj kod dostępu.\n\nUistite sa, že ste nastavili správny variant denníka v poli Variácia denníka UONET+ na prvej prihlasovacej obrazovke Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť Iné možnosti - V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie frekvencií, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení + V tomto režime nefungujú nasledovné: šťastné číslo, štatistiky triedy, zhrnutie dochádzky, ospravedlnenie neprítomnosti, dokončené lekcie, informácie o škole a prezeranie zoznamu registrovaných zariadení Tento režim zobrazuje rovnaké dáta, ktoré sa zobrazujú na webových stránkach denníka Kombinácia najlepších vlastností ostatných dvoch režimov. Funguje rýchlejšie ako scraper a poskytuje funkcie, ktoré nie sú k dispozícii v režime Mobilne API. Je to v experimentálnej fáze Ochrana osobných údajov @@ -264,7 +264,12 @@ Čas ukončenia Čas ukončenia musí byť neskorší ako čas začatia - Zhrnutie frekvencií + Zhrnutie dochádzky + Kalkulačka dochádzky + %1$d nad cieľom + presne v cieli + %1$d pod cieľom + %1$d/%2$d prítomnosti Neprítomnosť zo školských dôvodov Ospravedlnená neprítomnosť Neospravedlnená neprítomnosť @@ -282,22 +287,22 @@ Musíte vybrať aspoň jednu neprítomnosť! Ospravedlniť - Nová frekvencia - Nové frekvencie - Nové frekvencie - Nové frekvencie + Nová dochádzka + Nové dochádzky + Nové dochádzky + Nové dochádzky - %1$d nová frekvencia - %1$d nové frekvencie - %1$d nových frekvencií - %1$d nových frekvencií + %1$d nová dochádzka + %1$d nové dochádzky + %1$d nových dochádzok + %1$d nových dochádzok - %d frekvencia - %d frekvencie - %d frekvencií - %d frekvencií + %d dochádzka + %d dochádzky + %d dochádzok + %d dochádzok Spoločne @@ -731,6 +736,8 @@ Možnosti vypočítaného priemeru Vynútiť priemerný výpočet podľa aplikácie Zobraziť prítomnosť + Cieľová dochádzka + Triedenie kalkulačky dochádzky Motív Rozvijanie známok Zobraziť skupiny vedľa predmetov @@ -797,7 +804,7 @@ Známky Domov Viditeľnosť dlaždíc - Frekvencia + Dochádzka Plán lekcie Známky Vypočítaný priemer @@ -825,7 +832,7 @@ Nadchádzajúce lekcie Ladenie Zmeny plánu lekcií - Nové frekvencie + Nové dochádzky Čierna Červená diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index f0cfdd122..c32eedb96 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -1,5 +1,10 @@ + За алфавітом + За датою + За середньою + За відсотком відвідуваності + За балансом відвідування теми Світла Темна diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a0d4b6c0b..2d8ac1f4d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -265,6 +265,11 @@ Час завершення має бути пізніше часу початку Підсумок відвідуваності + Калькулятор відвідуваності + %1$d понад ціль + точно у цілі + %1$d під ціллю + %1$d/%2$d відвідуваності Відсутність зі шкільних причин Відсутність з поважних причин Відсутність без поважних причин @@ -731,6 +736,8 @@ Параметри розраховування середніх оцінок Примусово розраховувати середню оцінку через додаток Показувати присутність + Цільова відвідуваність + Сортування калькулятора відвідування Тема Розгортання оцінок Показувати групи поруч з темами From 95e41b5570a0c143ab81e576222a625b77eac90c Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:19:24 +0100 Subject: [PATCH 440/545] Handle subjects with no attendances in attendance calculator better (#2478) --------- Co-authored-by: Faierbel --- .../java/io/github/wulkanowy/data/Resource.kt | 3 ++ .../repositories/PreferencesRepository.kt | 9 ++++ .../GetAttendanceCalculatorDataUseCase.kt | 3 ++ .../calculator/AttendanceCalculatorAdapter.kt | 54 ++++++++++--------- .../main/res/values/preferences_defaults.xml | 1 + app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + .../res/xml/scheme_preferences_appearance.xml | 5 ++ 8 files changed, 53 insertions(+), 25 deletions(-) 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 c698c42d5..b4982b9a0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -71,6 +71,9 @@ fun Resource.mapData(block: (T) -> U) = when (this) { is Resource.Error -> Resource.Error(this.error) } +/** + * Injects another flow into this flow's resource data. + */ inline fun Flow>.combineWithResourceData( flow: Flow, crossinline block: suspend (T1, T2) -> R 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 4735293c0..2bb1538cb 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 @@ -54,6 +54,15 @@ class PreferencesRepository @Inject constructor( context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode) ).asFlow().map(AttendanceCalculatorSortingMode::getByValue) + /** + * Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0). + */ + val attendanceCalculatorShowEmptySubjects: Flow + get() = flowSharedPref.getBoolean( + context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects), + context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects) + ).asFlow() + private val gradeAverageModePref: Preference get() = getObjectFlow( R.string.pref_key_grade_average_mode, diff --git a/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt index ea68050d5..294abd1be 100644 --- a/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt +++ b/app/src/main/java/io/github/wulkanowy/domain/attendance/GetAttendanceCalculatorDataUseCase.kt @@ -49,6 +49,9 @@ class GetAttendanceCalculatorDataUseCase @Inject constructor( // intermediates that will be visible for barely any time. .debounceIntermediates() } + .combineWithResourceData(preferencesRepository.attendanceCalculatorShowEmptySubjects) { attendanceDataList, showEmptySubjects -> + attendanceDataList.filter { it.total != 0 || showEmptySubjects } + } .combineWithResourceData(preferencesRepository.attendanceCalculatorSortingModeFlow, List::sortedBy) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt index 73c08fd32..4b908bba8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorAdapter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.attendance.calculator -import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible @@ -19,42 +18,47 @@ class AttendanceCalculatorAdapter @Inject constructor() : override fun getItemCount() = items.size - override fun onCreateViewHolder( - parent: ViewGroup, viewType: Int - ) = ViewHolder( + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( ItemAttendanceCalculatorHeaderBinding.inflate( LayoutInflater.from(parent.context), parent, false ) ) - @SuppressLint("SetTextI18n") override fun onBindViewHolder(parent: ViewHolder, position: Int) { + val context = parent.binding.root.context + val item = items[position] + with(parent.binding) { - val item = items[position] attendanceCalculatorPercentage.text = "${item.presencePercentage.roundToInt()}" - if (item.lessonBalance > 0) { - attendanceCalculatorSummaryBalance.text = root.context.getString( - R.string.attendance_calculator_summary_balance_positive, - item.lessonBalance - ) - } else if (item.lessonBalance < 0) { - attendanceCalculatorSummaryBalance.text = root.context.getString( - R.string.attendance_calculator_summary_balance_negative, - abs(item.lessonBalance) - ) - } else { - attendanceCalculatorSummaryBalance.text = root.context.getString( - R.string.attendance_calculator_summary_balance_neutral, - ) + attendanceCalculatorSummaryBalance.text = when { + item.lessonBalance > 0 -> { + context.getString( + R.string.attendance_calculator_summary_balance_positive, + item.lessonBalance + ) + } + + item.lessonBalance < 0 -> { + context.getString( + R.string.attendance_calculator_summary_balance_negative, + abs(item.lessonBalance) + ) + } + + else -> context.getString(R.string.attendance_calculator_summary_balance_neutral) } attendanceCalculatorWarning.isVisible = item.lessonBalance < 0 attendanceCalculatorTitle.text = item.subjectName - attendanceCalculatorSummaryValues.text = root.context.getString( - R.string.attendance_calculator_summary_values, - item.presences, - item.total - ) + attendanceCalculatorSummaryValues.text = if (item.total == 0) { + context.getString(R.string.attendance_calculator_summary_values_empty) + } else { + context.getString( + R.string.attendance_calculator_summary_values, + item.presences, + item.total + ) + } } } diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 109418893..2981e1845 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -4,6 +4,7 @@ true 50 alphabetic + false only_one_semester false one diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index e95c59405..080456ef9 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -4,6 +4,7 @@ attendance_present attendance_target attendance_calculator_sorting_mode + attendance_calculator_show_empty_subjects app_theme dashboard_tiles grade_color_scheme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae6d91408..56cf94f04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -263,6 +263,7 @@ right on target %1$d under target %1$d/%2$d presences + No attendances recorded Absent for school reasons Excused absence Unexcused absence @@ -721,6 +722,7 @@ Force average calculation by app Show presence Attendance target + Show subjects without any attendances Attendance calculator sorting Theme Grades expanding diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index 46a0e6a92..a05d95c04 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -104,6 +104,11 @@ app:key="@string/pref_key_attendance_calculator_sorting_mode" app:title="@string/pref_view_attendance_calculator_sorting_mode" app:useSimpleSummaryProvider="true" /> + Date: Mon, 11 Mar 2024 23:38:17 +0100 Subject: [PATCH 441/545] Fix lateness color in attendance (#2481) --- .../modules/attendance/AttendanceAdapter.kt | 25 +++++++++++-------- app/src/main/res/values/colors.xml | 5 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 4e9baac3a..f5689ec8d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance +import android.content.res.ColorStateList import android.graphics.Typeface import android.view.LayoutInflater import android.view.View @@ -33,17 +34,17 @@ class AttendanceAdapter @Inject constructor() : ) override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val context = holder.binding.root.context val item = items[position] with(holder.binding) { attendanceItemNumber.text = item.number.toString() - attendanceItemSubject.text = item.subject.ifBlank { - root.context.getString(R.string.all_no_data) - } + attendanceItemSubject.text = item.subject + .ifBlank { context.getString(R.string.all_no_data) } attendanceItemDescription.setText(item.descriptionRes) attendanceItemDescription.setTextColor( - root.context.getThemeAttrColor( + context.getThemeAttrColor( when { item.absence && !item.excused -> R.attr.colorAttendanceAbsence item.lateness && !item.excused -> R.attr.colorAttendanceLateness @@ -61,13 +62,15 @@ class AttendanceAdapter @Inject constructor() : attendanceItemAlert.isVisible = item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) } - attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor( - when{ - item.absence && !item.excused -> R.attr.colorAttendanceAbsence - item.lateness && !item.excused -> R.attr.colorAttendanceLateness - else -> android.R.attr.colorPrimary - } - )) + attendanceItemAlert.imageTintList = ColorStateList.valueOf( + context.getThemeAttrColor( + when { + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.colorPrimary + } + ) + ) attendanceItemNumber.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.GONE diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8ad27ad88..f31a5f947 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -46,14 +46,15 @@ #d32f2f #e57373 + #ff8f00 #ffd54f #d32f2f #e57373 - #cd2a01 - #f05d0e + #ff8f00 + #ffd54f #1f000000 #1fffffff From 6a8f6f9496fdd29337b8f1352390beffb3600ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 23:38:39 +0100 Subject: [PATCH 442/545] Add WulkanowySdkFactory (#2479) --- .../io/github/wulkanowy/data/DataModule.kt | 21 ------ .../wulkanowy/data/WulkanowySdkFactory.kt | 64 +++++++++++++++++++ .../data/repositories/AttendanceRepository.kt | 18 +++--- .../AttendanceSummaryRepository.kt | 11 ++-- .../CompletedLessonsRepository.kt | 9 +-- .../data/repositories/ConferenceRepository.kt | 9 +-- .../data/repositories/ExamRepository.kt | 9 +-- .../data/repositories/GradeRepository.kt | 9 +-- .../repositories/GradeStatisticsRepository.kt | 15 ++--- .../data/repositories/HomeworkRepository.kt | 9 +-- .../repositories/LuckyNumberRepository.kt | 9 +-- .../data/repositories/MessageRepository.kt | 59 +++++++++-------- .../repositories/MobileDeviceRepository.kt | 15 ++--- .../data/repositories/NoteRepository.kt | 9 +-- .../data/repositories/RecipientRepository.kt | 10 +-- .../data/repositories/RecoverRepository.kt | 20 ++++-- .../SchoolAnnouncementRepository.kt | 7 +- .../data/repositories/SchoolRepository.kt | 9 +-- .../data/repositories/SchoolsRepository.kt | 11 ++-- .../data/repositories/SemesterRepository.kt | 9 ++- .../repositories/StudentInfoRepository.kt | 12 ++-- .../data/repositories/StudentRepository.kt | 17 ++--- .../data/repositories/SubjectRepository.kt | 9 +-- .../data/repositories/TeacherRepository.kt | 9 +-- .../data/repositories/TimetableRepository.kt | 9 +-- .../ui/modules/captcha/CaptchaDialog.kt | 6 +- .../io/github/wulkanowy/utils/SdkExtension.kt | 42 ------------ .../wulkanowy/WulkanowySdkFactoryCreator.kt | 12 ++++ .../repositories/AttendanceRepositoryTest.kt | 10 +-- .../CompletedLessonsRepositoryTest.kt | 9 +-- .../data/repositories/ExamRemoteTest.kt | 9 +-- .../data/repositories/GradeRepositoryTest.kt | 9 +-- .../GradeStatisticsRepositoryTest.kt | 16 +++-- .../repositories/LuckyNumberRemoteTest.kt | 9 +-- .../repositories/MessageRepositoryTest.kt | 11 ++-- .../MobileDeviceRepositoryTest.kt | 10 +-- .../data/repositories/RecipientLocalTest.kt | 16 +++-- .../repositories/SemesterRepositoryTest.kt | 10 +-- .../repositories/TimetableRepositoryTest.kt | 9 +-- 39 files changed, 283 insertions(+), 283 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt create mode 100644 app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 50d6c8f9f..a492c08db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -18,17 +18,13 @@ import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.RemoteConfigHelper -import io.github.wulkanowy.utils.WebkitCookieManagerProxy import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.create -import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -36,23 +32,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal class DataModule { - @Singleton - @Provides - fun provideSdk( - chuckerInterceptor: ChuckerInterceptor, - remoteConfig: RemoteConfigHelper, - webkitCookieManagerProxy: WebkitCookieManagerProxy, - ) = Sdk().apply { - androidVersion = android.os.Build.VERSION.RELEASE - buildTag = android.os.Build.MODEL - userAgentTemplate = remoteConfig.userAgentTemplate - setSimpleHttpLogger { Timber.d(it) } - setAdditionalCookieManager(webkitCookieManagerProxy) - - // for debug only - addInterceptor(chuckerInterceptor, network = true) - } - @Singleton @Provides fun provideChuckerCollector( diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt new file mode 100644 index 000000000..6d4f9edad --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -0,0 +1,64 @@ +package io.github.wulkanowy.data + +import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.RemoteConfigHelper +import io.github.wulkanowy.utils.WebkitCookieManagerProxy +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WulkanowySdkFactory @Inject constructor( + private val chuckerInterceptor: ChuckerInterceptor, + private val remoteConfig: RemoteConfigHelper, + private val webkitCookieManagerProxy: WebkitCookieManagerProxy +) { + + private val sdk = Sdk().apply { + androidVersion = android.os.Build.VERSION.RELEASE + buildTag = android.os.Build.MODEL + userAgentTemplate = remoteConfig.userAgentTemplate + setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(webkitCookieManagerProxy) + + // for debug only + addInterceptor(chuckerInterceptor, network = true) + } + + fun create() = sdk + + fun create(student: Student, semester: Semester? = null): Sdk { + return create().apply { + email = student.email + password = student.password + symbol = student.symbol + schoolSymbol = student.schoolSymbol + studentId = student.studentId + classId = student.classId + emptyCookieJarInterceptor = true + + if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { + mobileBaseUrl = student.mobileBaseUrl + } else { + scrapperBaseUrl = student.scrapperBaseUrl + domainSuffix = student.scrapperDomainSuffix + loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) + } + + mode = Sdk.Mode.valueOf(student.loginMode) + mobileBaseUrl = student.mobileBaseUrl + keyId = student.certificateKey + privatePem = student.privateKey + + if (semester != null) { + diaryId = semester.diaryId + kindergartenDiaryId = semester.kindergartenDiaryId + schoolYear = semester.schoolYear + unitId = semester.unitId + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 46ea29f83..9b94cc103 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance @@ -7,14 +8,11 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -28,7 +26,7 @@ import javax.inject.Singleton class AttendanceRepository @Inject constructor( private val attendanceDb: AttendanceDao, private val timetableDb: TimetableDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -59,8 +57,7 @@ class AttendanceRepository @Inject constructor( val lessons = timetableDb.load( semester.diaryId, semester.studentId, start.monday, end.sunday ) - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getAttendance(start.monday, end.sunday) .mapToEntities(semester, lessons) }, @@ -90,8 +87,10 @@ class AttendanceRepository @Inject constructor( } suspend fun excuseForAbsence( - student: Student, semester: Semester, - absenceList: List, reason: String? = null + student: Student, + semester: Semester, + absenceList: List, + reason: String? = null ) { val items = absenceList.map { attendance -> Absent( @@ -99,8 +98,7 @@ class AttendanceRepository @Inject constructor( timeId = attendance.timeId ) } - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .excuseForAbsence(items, reason) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index 1129598ac..78c98169b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories import androidx.room.withTransaction -import io.github.wulkanowy.data.* +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -20,9 +18,9 @@ import javax.inject.Singleton @Singleton class AttendanceSummaryRepository @Inject constructor( private val attendanceDb: AttendanceSummaryDao, - private val sdk: Sdk, private val refreshHelper: AutoRefreshHelper, private val appDatabase: AppDatabase, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -43,8 +41,7 @@ class AttendanceSummaryRepository @Inject constructor( }, query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getAttendanceSummary(subjectId) .mapToEntities(semester, subjectId) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index f7f86b23d..45a36f55c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class CompletedLessonsRepository @Inject constructor( private val completedLessonsDb: CompletedLessonsDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -53,8 +51,7 @@ class CompletedLessonsRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getCompletedLessons(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index fbe578604..58ce0091a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -1,16 +1,14 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class ConferenceRepository @Inject constructor( private val conferenceDb: ConferenceDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -46,8 +44,7 @@ class ConferenceRepository @Inject constructor( conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getConferences() .mapToEntities(semester) .filter { it.date >= startDate } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 9b8dd02e3..89dbcd5ce 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -1,18 +1,16 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.endExamsDay import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.startExamsDay -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -23,7 +21,7 @@ import javax.inject.Singleton @Singleton class ExamRepository @Inject constructor( private val examDb: ExamDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -56,8 +54,7 @@ class ExamRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getExams(start.startExamsDay, start.endExamsDay) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index ac1ef541b..e899f900d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao @@ -10,11 +11,8 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.toLocalDate import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -30,7 +28,7 @@ class GradeRepository @Inject constructor( private val gradeDb: GradeDao, private val gradeSummaryDb: GradeSummaryDao, private val gradeDescriptiveDb: GradeDescriptiveDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -63,8 +61,7 @@ class GradeRepository @Inject constructor( } }, fetch = { - val (details, summary, descriptive) = sdk.init(student) - .switchSemester(semester) + val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester) .getGrades(semester.semesterId) Triple( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 809f92d3e..f120d34f3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao @@ -12,11 +13,8 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.util.Locale @@ -28,7 +26,7 @@ class GradeStatisticsRepository @Inject constructor( private val gradePartialStatisticsDb: GradePartialStatisticsDao, private val gradePointsStatisticsDb: GradePointsStatisticsDao, private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -56,8 +54,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesPartialStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -104,8 +101,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesSemesterStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -163,8 +159,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesPointsStatistics(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 1a9c7ffaf..7893ef631 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -1,18 +1,16 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -22,7 +20,7 @@ import javax.inject.Singleton @Singleton class HomeworkRepository @Inject constructor( private val homeworkDb: HomeworkDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -55,8 +53,7 @@ class HomeworkRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getHomework(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 45b7f6e29..3636cb51e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -1,12 +1,11 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex @@ -18,7 +17,7 @@ import javax.inject.Singleton @Singleton class LuckyNumberRepository @Inject constructor( private val luckyNumberDb: LuckyNumberDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -33,7 +32,9 @@ class LuckyNumberRepository @Inject constructor( shouldFetch = { it == null || forceRefresh }, query = { luckyNumberDb.load(student.studentId, now()) }, fetch = { - sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) + wulkanowySdkFactory.create(student) + .getLuckyNumber(student.schoolShortName) + ?.mapToEntity(student) }, saveFetchResult = { oldLuckyNumber, newLuckyNumber -> newLuckyNumber ?: return@networkBoundResource 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 a4517760b..ede2a0fde 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 @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao @@ -29,11 +30,9 @@ import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.toFirstResult 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 import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -48,7 +47,7 @@ class MessageRepository @Inject constructor( private val messagesDb: MessagesDao, private val mutedMessageSendersDao: MutedMessageSendersDao, private val messageAttachmentDao: MessageAttachmentDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, @ApplicationContext private val context: Context, private val refreshHelper: AutoRefreshHelper, private val sharedPrefProvider: SharedPrefProvider, @@ -82,10 +81,16 @@ class MessageRepository @Inject constructor( } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id) }, fetch = { - sdk.init(student).getMessages( - folder = Folder.valueOf(folder.name), - mailboxKey = mailbox?.globalKey, - ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email)) + wulkanowySdkFactory.create(student) + .getMessages( + folder = Folder.valueOf(folder.name), + mailboxKey = mailbox?.globalKey, + ) + .mapToEntities( + student = student, + mailbox = mailbox, + allMailboxes = mailboxDao.loadAll(student.email) + ) }, saveFetchResult = { oldWithAuthors, new -> val old = oldWithAuthors.map { it.message } @@ -115,10 +120,11 @@ class MessageRepository @Inject constructor( }, query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, fetch = { - sdk.init(student).getMessageDetails( - messageKey = it!!.message.messageGlobalKey, - markAsRead = message.unread && markAsRead, - ) + wulkanowySdkFactory.create(student) + .getMessageDetails( + messageKey = it!!.message.messageGlobalKey, + markAsRead = message.unread && markAsRead, + ) }, saveFetchResult = { old, new -> checkNotNull(old) { "Fetched message no longer exist!" } @@ -159,19 +165,19 @@ class MessageRepository @Inject constructor( recipients: List, mailbox: Mailbox, ) { - sdk.init(student).sendMessage( - subject = subject, - content = content, - recipients = recipients.mapFromEntities(), - mailboxId = mailbox.globalKey, - ) + wulkanowySdkFactory.create(student) + .sendMessage( + subject = subject, + content = content, + recipients = recipients.mapFromEntities(), + mailboxId = mailbox.globalKey, + ) refreshFolders(student, mailbox, listOf(SENT)) } suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) { - sdk.init(student).restoreMessages( - messages = messages.map { it.messageGlobalKey }, - ) + wulkanowySdkFactory.create(student) + .restoreMessages(messages = messages.map { it.messageGlobalKey }) refreshFolders(student, mailbox) } @@ -182,10 +188,11 @@ class MessageRepository @Inject constructor( suspend fun deleteMessages(student: Student, messages: List) { val firstMessage = messages.first() - sdk.init(student).deleteMessages( - messages = messages.map { it.messageGlobalKey }, - removeForever = firstMessage.folderId == TRASHED.id, - ) + wulkanowySdkFactory.create(student) + .deleteMessages( + messages = messages.map { it.messageGlobalKey }, + removeForever = firstMessage.folderId == TRASHED.id, + ) if (firstMessage.folderId != TRASHED.id) { val deletedMessages = messages.map { @@ -230,7 +237,9 @@ class MessageRepository @Inject constructor( }, query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, fetch = { - sdk.init(student).getMailboxes().mapToEntities(student) + wulkanowySdkFactory.create(student) + .getMailboxes() + .mapToEntities(student) }, saveFetchResult = { old, new -> mailboxDao.deleteAll(old uniqueSubtract new) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 48b4fc287..19466554a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.Semester @@ -8,11 +9,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class MobileDeviceRepository @Inject constructor( private val mobileDb: MobileDeviceDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -42,8 +40,7 @@ class MobileDeviceRepository @Inject constructor( }, query = { mobileDb.loadAll(student.userLoginId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getRegisteredDevices() .mapToEntities(student) }, @@ -57,16 +54,14 @@ class MobileDeviceRepository @Inject constructor( ) suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .unregisterDevice(device.deviceId) mobileDb.deleteAll(listOf(device)) } suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { - return sdk.init(student) - .switchSemester(semester) + return wulkanowySdkFactory.create(student, semester) .getToken() .mapToMobileDeviceToken() } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index feb92c154..9551e01eb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -1,16 +1,14 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.toLocalDate import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class NoteRepository @Inject constructor( private val noteDb: NoteDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -45,8 +43,7 @@ class NoteRepository @Inject constructor( }, query = { noteDb.loadAll(student.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getNotes() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt index 4a1474ced..8233d932e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.MailboxType @@ -7,10 +8,8 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -18,14 +17,15 @@ import javax.inject.Singleton @Singleton class RecipientRepository @Inject constructor( private val recipientDb: RecipientDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { private val cacheKey = "recipient" suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) { - val new = sdk.init(student).getRecipients(mailbox.globalKey) + val new = wulkanowySdkFactory.create(student) + .getRecipients(mailbox.globalKey) .mapToEntities(mailbox.globalKey) val old = recipientDb.loadAll(type, mailbox.globalKey) @@ -60,7 +60,7 @@ class RecipientRepository @Inject constructor( ): List { mailbox ?: return emptyList() - return sdk.init(student) + return wulkanowySdkFactory.create(student) .getMessageReplayDetails(message.messageGlobalKey) .sender .let(::listOf) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt index 5940f477b..b554bda0f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt @@ -1,17 +1,23 @@ package io.github.wulkanowy.data.repositories -import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.data.WulkanowySdkFactory import javax.inject.Inject import javax.inject.Singleton @Singleton -class RecoverRepository @Inject constructor(private val sdk: Sdk) { +class RecoverRepository @Inject constructor( + private val wulkanowySdkFactory: WulkanowySdkFactory +) { - suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair { - return sdk.getPasswordResetCaptchaCode(host, symbol) - } + suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair = + wulkanowySdkFactory.create() + .getPasswordResetCaptchaCode(host, symbol) suspend fun sendRecoverRequest( - url: String, symbol: String, email: String, reCaptchaResponse: String - ): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) + url: String, + symbol: String, + email: String, + reCaptchaResponse: String + ): String = wulkanowySdkFactory.create() + .sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index f09a46aa1..6a04ce75f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -1,14 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -18,7 +17,7 @@ import javax.inject.Singleton @Singleton class SchoolAnnouncementRepository @Inject constructor( private val schoolAnnouncementDb: SchoolAnnouncementDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -41,7 +40,7 @@ class SchoolAnnouncementRepository @Inject constructor( schoolAnnouncementDb.loadAll(student.userLoginId) }, fetch = { - val sdk = sdk.init(student) + val sdk = wulkanowySdkFactory.create(student) val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student) val directorInformation = sdk.getDirectorInformation().mapToEntities(student) lastAnnouncements + directorInformation diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index b42b4d577..c48abb6f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -17,7 +15,7 @@ import javax.inject.Singleton @Singleton class SchoolRepository @Inject constructor( private val schoolDb: SchoolDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -40,8 +38,7 @@ class SchoolRepository @Inject constructor( }, query = { schoolDb.load(semester.studentId, semester.classId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getSchool() .mapToEntity(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt index 216a8c112..4a16d6f13 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.pojos.IntegrityRequest import io.github.wulkanowy.data.pojos.LoginEvent -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.IntegrityHelper import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withTimeout import timber.log.Timber import java.util.UUID @@ -23,7 +21,7 @@ import kotlin.time.Duration.Companion.seconds class SchoolsRepository @Inject constructor( private val integrityHelper: IntegrityHelper, private val schoolsService: SchoolsService, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { suspend fun logSchoolLogin(loginData: LoginData, students: List) { @@ -40,10 +38,9 @@ class SchoolsRepository @Inject constructor( private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) { val requestId = UUID.randomUUID().toString() val token = integrityHelper.getIntegrityToken(requestId) ?: return + val updatedStudent = student.copy(password = loginData.password) - val schoolInfo = sdk - .init(student.copy(password = loginData.password)) - .switchSemester(semester) + val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester) .getSchool() schoolsService.logLoginEvent( 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 9ae22babc..da21f59ac 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -7,7 +8,6 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.withContext @@ -18,7 +18,7 @@ import javax.inject.Singleton @Singleton class SemesterRepository @Inject constructor( private val semesterDb: SemesterDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val dispatchers: DispatchersProvider, ) { @@ -60,7 +60,10 @@ class SemesterRepository @Inject constructor( } private suspend fun refreshSemesters(student: Student) { - val new = sdk.init(student).getSemesters().mapToEntities(student.studentId) + val new = wulkanowySdkFactory.create(student) + .getSemesters() + .mapToEntities(student.studentId) + if (new.isEmpty()) return Timber.i("Empty semester list!") val old = semesterDb.loadAll(student.studentId, student.classId) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index d42be180d..db4c0aebb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -1,13 +1,11 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.StudentInfoDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -15,7 +13,7 @@ import javax.inject.Singleton @Singleton class StudentInfoRepository @Inject constructor( private val studentInfoDao: StudentInfoDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -30,9 +28,9 @@ class StudentInfoRepository @Inject constructor( shouldFetch = { it == null || forceRefresh }, query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) - .getStudentInfo().mapToEntity(semester) + wulkanowySdkFactory.create(student, semester) + .getStudentInfo() + .mapToEntity(semester) }, saveFetchResult = { old, new -> if (old != null && new != old) { 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 e063840cb..9a5ecd538 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import androidx.room.withTransaction +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao @@ -14,9 +15,7 @@ import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.security.Scrambler -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -26,7 +25,7 @@ class StudentRepository @Inject constructor( private val dispatchers: DispatchersProvider, private val studentDb: StudentDao, private val semesterDb: SemesterDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val appDatabase: AppDatabase, private val scrambler: Scrambler, ) { @@ -37,7 +36,7 @@ class StudentRepository @Inject constructor( pin: String, symbol: String, token: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getStudentsFromHebe(token, pin, symbol, "") .mapToPojo(null) @@ -47,7 +46,7 @@ class StudentRepository @Inject constructor( scrapperBaseUrl: String, domainSuffix: String, symbol: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol) .mapToPojo(password) @@ -56,7 +55,7 @@ class StudentRepository @Inject constructor( password: String, scrapperBaseUrl: String, symbol: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) .mapToPojo(password) @@ -149,13 +148,11 @@ class StudentRepository @Inject constructor( .distinctBy { it.student.studentName }.size == 1 suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .authorizePermission(pesel) suspend fun refreshStudentName(student: Student, semester: Semester) { - val newCurrentApiStudent = sdk.init(student) - .switchSemester(semester) + val newCurrentApiStudent = wulkanowySdkFactory.create(student, semester) .getCurrentStudent() ?: return val studentName = StudentName( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index cf7f86c22..573c7c149 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -18,7 +16,7 @@ import javax.inject.Singleton @Singleton class SubjectRepository @Inject constructor( private val subjectDao: SubjectDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -39,8 +37,7 @@ class SubjectRepository @Inject constructor( }, query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getSubjects() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 5a488b27c..a5a6e3f9c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.TeacherDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -18,7 +16,7 @@ import javax.inject.Singleton @Singleton class TeacherRepository @Inject constructor( private val teacherDb: TeacherDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -39,8 +37,7 @@ class TeacherRepository @Inject constructor( }, query = { teacherDb.loadAll(semester.studentId, semester.classId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getTeachers() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 0d208c1fc..335789991 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao @@ -11,14 +12,11 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -33,7 +31,7 @@ class TimetableRepository @Inject constructor( private val timetableDb: TimetableDao, private val timetableAdditionalDb: TimetableAdditionalDao, private val timetableHeaderDb: TimetableHeaderDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val schedulerHelper: TimetableNotificationSchedulerHelper, private val refreshHelper: AutoRefreshHelper, ) { @@ -74,8 +72,7 @@ class TimetableRepository @Inject constructor( }, query = { getFullTimetableFromDatabase(student, semester, start, end) }, fetch = { - val timetableFull = sdk.init(student) - .switchSemester(semester) + val timetableFull = wulkanowySdkFactory.create(student, semester) .getTimetable(start.monday, end.sunday) timetableFull.mapToEntities(semester) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt index 98b4fda71..ce2173d28 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -10,8 +10,8 @@ import android.webkit.WebViewClient import androidx.core.os.bundleOf import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.databinding.DialogCaptchaBinding -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.WebkitCookieManagerProxy import timber.log.Timber @@ -21,7 +21,7 @@ import javax.inject.Inject class CaptchaDialog : BaseDialogFragment() { @Inject - lateinit var sdk: Sdk + lateinit var wulkanowySdkFactory: WulkanowySdkFactory @Inject lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy @@ -59,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment() { webView = this with(settings) { javaScriptEnabled = true - userAgentString = sdk.userAgent + userAgentString = wulkanowySdkFactory.create().userAgent } webViewClient = object : WebViewClient() { diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt deleted file mode 100644 index 9b6ca7060..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.utils - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import timber.log.Timber - -fun Sdk.init(student: Student): Sdk { - email = student.email - password = student.password - symbol = student.symbol - schoolSymbol = student.schoolSymbol - studentId = student.studentId - classId = student.classId - emptyCookieJarInterceptor = true - - if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { - mobileBaseUrl = student.mobileBaseUrl - } else { - scrapperBaseUrl = student.scrapperBaseUrl - domainSuffix = student.scrapperDomainSuffix - loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) - } - - mode = Sdk.Mode.valueOf(student.loginMode) - mobileBaseUrl = student.mobileBaseUrl - keyId = student.certificateKey - privatePem = student.privateKey - - Timber.d("Sdk in ${student.loginMode} mode reinitialized") - - return this -} - -fun Sdk.switchSemester(semester: Semester): Sdk { - return switchDiary( - diaryId = semester.diaryId, - kindergartenDiaryId = semester.kindergartenDiaryId, - schoolYear = semester.schoolYear, - unitId = semester.unitId, - ) -} diff --git a/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt new file mode 100644 index 000000000..dd1ce0569 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.sdk.Sdk +import io.mockk.every +import io.mockk.mockk + +fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk() + .apply { + every { create() } returns sdk + every { create(any(), any()) } answers { callOriginal() } + } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index e64144c2f..b34902363 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.TimetableDao @@ -16,8 +17,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -30,8 +31,8 @@ import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance class AttendanceRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var attendanceDb: AttendanceDao @@ -63,7 +64,8 @@ class AttendanceRepositoryTest { every { refreshHelper.shouldBeRefreshed(any()) } returns false coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList() - attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper) + attendanceRepository = + AttendanceRepository(attendanceDb, timetableDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt index f8f688501..e20603d22 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.errorOrNull @@ -15,8 +16,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -28,8 +29,8 @@ import io.github.wulkanowy.sdk.pojo.CompletedLesson as SdkCompletedLesson class CompletedLessonsRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var completedLessonDb: CompletedLessonsDao @@ -58,7 +59,7 @@ class CompletedLessonsRepositoryTest { every { refreshHelper.shouldBeRefreshed(any()) } returns false completedLessonRepository = - CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper) + CompletedLessonsRepository(completedLessonDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index d1ed9ca32..671c66f95 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.errorOrNull @@ -15,8 +16,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -28,8 +29,8 @@ import io.github.wulkanowy.sdk.pojo.Exam as SdkExam class ExamRemoteTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var examDb: ExamDao @@ -59,7 +60,7 @@ class ExamRemoteTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - examRepository = ExamRepository(examDb, sdk, refreshHelper) + examRepository = ExamRepository(examDb, wulkanowySdkFactory, refreshHelper) } @Test 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 0ea5d3fa4..0045badf1 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao @@ -18,8 +19,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -35,8 +36,8 @@ import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade class GradeRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var gradeDb: GradeDao @@ -65,7 +66,7 @@ class GradeRepositoryTest { gradeDb = gradeDb, gradeSummaryDb = gradeSummaryDb, gradeDescriptiveDb = gradeDescriptiveDb, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, refreshHelper = refreshHelper, ) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index dfd36ee1a..6733190b7 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao @@ -13,9 +14,14 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -24,8 +30,8 @@ import org.junit.Test class GradeStatisticsRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var gradePartialStatisticsDb: GradePartialStatisticsDao @@ -54,7 +60,7 @@ class GradeStatisticsRepositoryTest { gradePartialStatisticsDb = gradePartialStatisticsDb, gradePointsStatisticsDb = gradePointsStatisticsDb, gradeSemesterStatisticsDb = gradeSemesterStatisticsDb, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, refreshHelper = refreshHelper, ) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index fa78b1bd3..854d5d548 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.errorOrNull @@ -12,8 +13,8 @@ import io.mockk.Runs import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -25,8 +26,8 @@ import io.github.wulkanowy.sdk.pojo.LuckyNumber as SdkLuckyNumber class LuckyNumberRemoteTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var luckyNumberDb: LuckyNumberDao @@ -43,7 +44,7 @@ class LuckyNumberRemoteTest { fun setUp() { MockKAnnotations.init(this) - luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, sdk) + luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, wulkanowySdkFactory) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index fbbe49345..9819fb1f7 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import android.content.Context +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao @@ -28,10 +29,9 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking @@ -45,11 +45,10 @@ import java.time.Instant import java.time.ZoneOffset import kotlin.test.assertTrue -@OptIn(ExperimentalCoroutinesApi::class) class MessageRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var messageDb: MessagesDao @@ -102,7 +101,7 @@ class MessageRepositoryTest { messagesDb = messageDb, mutedMessageSendersDao = mutesDb, messageAttachmentDao = messageAttachmentDao, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, context = context, refreshHelper = refreshHelper, sharedPrefProvider = sharedPrefProvider, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index aa93a5e6f..5513a95fe 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.errorOrNull @@ -16,8 +17,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Assert @@ -28,8 +29,8 @@ import java.time.ZonedDateTime.of class MobileDeviceRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var mobileDeviceDb: MobileDeviceDao @@ -53,7 +54,8 @@ class MobileDeviceRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - mobileDeviceRepository = MobileDeviceRepository(mobileDeviceDb, sdk, refreshHelper) + mobileDeviceRepository = + MobileDeviceRepository(mobileDeviceDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt index e608cafb1..0ecaad9ea 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getMailboxEntity @@ -7,9 +8,14 @@ import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.MailboxType import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before @@ -18,8 +24,8 @@ import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient class RecipientLocalTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var recipientDb: RecipientDao @@ -63,7 +69,7 @@ class RecipientLocalTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - recipientRepository = RecipientRepository(recipientDb, sdk, refreshHelper) + recipientRepository = RecipientRepository(recipientDb, wulkanowySdkFactory, refreshHelper) } @Test 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 96db8a794..3a18ac48a 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.TestDispatchersProvider +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getSemesterEntity @@ -12,8 +13,8 @@ import io.mockk.Runs import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -24,8 +25,8 @@ import java.time.LocalDate.now class SemesterRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var semesterDb: SemesterDao @@ -38,7 +39,8 @@ class SemesterRepositoryTest { fun initTest() { MockKAnnotations.init(this) - semesterRepository = SemesterRepository(semesterDb, sdk, TestDispatchersProvider()) + semesterRepository = + SemesterRepository(semesterDb, wulkanowySdkFactory, TestDispatchersProvider()) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index 2a61f99ce..a14605435 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao @@ -18,9 +19,9 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just import io.mockk.mockk +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -37,8 +38,8 @@ class TimetableRepositoryTest { @MockK(relaxed = true) private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var timetableDb: TimetableDao @@ -71,7 +72,7 @@ class TimetableRepositoryTest { timetableDb, timetableAdditionalDao, timetableHeaderDao, - sdk, + wulkanowySdkFactory, timetableNotificationSchedulerHelper, refreshHelper ) From 8a90b61b97bd4ed663d1d3b64543d26b0da8446a Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:01:00 +0100 Subject: [PATCH 443/545] Refactor networkBoundResource (#2482) --------- Co-authored-by: Faierbel --- .../java/io/github/wulkanowy/data/Resource.kt | 127 ++++++++---------- .../repositories/AdminMessageRepository.kt | 3 +- .../data/repositories/MessageRepository.kt | 2 +- .../io/github/wulkanowy/data/ResourceTest.kt | 10 +- 4 files changed, 62 insertions(+), 80 deletions(-) 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 b4982b9a0..7c6c2a9ff 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow @@ -22,15 +23,15 @@ import timber.log.Timber import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -sealed class Resource { +sealed interface Resource { - open class Loading : Resource() + open class Loading : Resource data class Intermediate(val data: T) : Loading() - data class Success(val data: T) : Resource() + data class Success(val data: T) : Resource - data class Error(val error: Throwable) : Resource() + data class Error(val error: Throwable) : Resource } val Resource.dataOrNull: T? @@ -97,7 +98,7 @@ fun Flow>.logResourceStatus(name: String, showData: Boolean = fa Timber.i("$name: $description") } -fun Flow>.mapResourceData(block: suspend (T) -> U) = map { +inline fun Flow>.mapResourceData(crossinline block: suspend (T) -> U) = map { when (it) { is Resource.Success -> Resource.Success(block(it.data)) is Resource.Intermediate -> Resource.Intermediate(block(it.data)) @@ -167,33 +168,32 @@ suspend fun Flow>.waitForResult() = takeWhile { it is Resource.L // Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired, // use `debounceIntermediates` to alleviate this behavior. -inline fun combineResourceFlows( - flows: Iterable>>, -): Flow>> = combine(flows) { items -> - var isIntermediate = false - val data = mutableListOf() - for (item in items) { - when (item) { - is Resource.Success -> data.add(item.data) - is Resource.Intermediate -> { - isIntermediate = true - data.add(item.data) - } +inline fun combineResourceFlows(flows: Iterable>>): Flow>> = + combine(flows) { items -> + var isIntermediate = false + val data = mutableListOf() + for (item in items) { + when (item) { + is Resource.Success -> data.add(item.data) + is Resource.Intermediate -> { + isIntermediate = true + data.add(item.data) + } - is Resource.Loading -> return@combine Resource.Loading() - is Resource.Error -> continue + is Resource.Loading -> return@combine Resource.Loading() + is Resource.Error -> continue + } + } + if (data.isEmpty()) { + // All items have to be errors for this to happen, so just return the first one. + // mapData is functionally useless and exists only to satisfy the type checker + items.first().mapData { listOf(it) } + } else if (isIntermediate) { + Resource.Intermediate(data) + } else { + Resource.Success(data) } } - if (data.isEmpty()) { - // All items have to be errors for this to happen, so just return the first one. - // mapData is functionally useless and exists only to satisfy the type checker - items.first().mapData { listOf(it) } - } else if (isIntermediate) { - Resource.Intermediate(data) - } else { - Resource.Success(data) - } -} @OptIn(FlowPreview::class) fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = flow { @@ -214,70 +214,51 @@ fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = }) } + inline fun networkBoundResource( mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, crossinline isResultEmpty: (ResultType) -> Boolean, crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, + crossinline fetch: suspend () -> RequestType, crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, crossinline shouldFetch: (ResultType) -> Boolean = { true }, crossinline filterResult: (ResultType) -> ResultType = { it } -) = flow { - emit(Resource.Loading()) - - val data = query().first() - emitAll(if (shouldFetch(data)) { - val filteredResult = filterResult(data) - - if (showSavedOnLoading && !isResultEmpty(filteredResult)) { - emit(Resource.Intermediate(filteredResult)) - } - - try { - val newData = fetch(data) - mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.Success(filterResult(it)) } - } catch (throwable: Throwable) { - onFetchFailed(throwable) - flowOf(Resource.Error(throwable)) - } - } else { - query().map { Resource.Success(filterResult(it)) } - }) -} +) = networkBoundResource( + mutex = mutex, + isResultEmpty = isResultEmpty, + query = query, + fetch = fetch, + saveFetchResult = saveFetchResult, + shouldFetch = shouldFetch, + mapResult = filterResult +) @JvmName("networkBoundResourceWithMap") -inline fun networkBoundResource( +inline fun networkBoundResource( mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, - crossinline isResultEmpty: (T) -> Boolean, + crossinline isResultEmpty: (MappedResultType) -> Boolean, crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, + crossinline fetch: suspend () -> RequestType, crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline mapResult: (ResultType) -> T, + crossinline mapResult: (ResultType) -> MappedResultType, ) = flow { emit(Resource.Loading()) val data = query().first() - emitAll(if (shouldFetch(data)) { - val mappedResult = mapResult(data) + if (shouldFetch(data)) { + emit(Resource.Intermediate(data)) - if (showSavedOnLoading && !isResultEmpty(mappedResult)) { - emit(Resource.Intermediate(mappedResult)) - } try { - val newData = fetch(data) + val newData = fetch() mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.Success(mapResult(it)) } } catch (throwable: Throwable) { - onFetchFailed(throwable) - flowOf(Resource.Error(throwable)) + emit(Resource.Error(throwable)) + return@flow } - } else { - query().map { Resource.Success(mapResult(it)) } - }) + } + + emitAll(query().map { Resource.Success(it) }) } + .mapResourceData { mapResult(it) } + .filterNot { it is Resource.Intermediate && isResultEmpty(it.data) } 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 b831ee755..aa0022b08 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 @@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.networkBoundResource import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -28,6 +29,6 @@ class AdminMessageRepository @Inject constructor( saveFetchResult = { oldItems, newItems -> adminMessageDao.removeOldAndSaveNew(oldItems, newItems) }, - showSavedOnLoading = false, ) + .filterNot { it is Resource.Intermediate } } 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 ede2a0fde..f91dc63e3 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 @@ -122,7 +122,7 @@ class MessageRepository @Inject constructor( fetch = { wulkanowySdkFactory.create(student) .getMessageDetails( - messageKey = it!!.message.messageGlobalKey, + messageKey = message.messageGlobalKey, markAsRead = message.unread && markAsRead, ) }, diff --git a/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt index ea846a57b..aa79a637b 100644 --- a/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt @@ -1,6 +1,10 @@ package io.github.wulkanowy.data -import io.mockk.* +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerifyOrder +import io.mockk.just +import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf @@ -42,7 +46,6 @@ class ResourceTest { // first networkBoundResource( isResultEmpty = { false }, - showSavedOnLoading = false, query = { repo.query() }, fetch = { val data = repo.fetch() @@ -57,7 +60,6 @@ class ResourceTest { // second networkBoundResource( isResultEmpty = { false }, - showSavedOnLoading = false, query = { repo.query() }, fetch = { val data = repo.fetch() @@ -124,7 +126,6 @@ class ResourceTest { networkBoundResource( isResultEmpty = { false }, mutex = saveResultMutex, - showSavedOnLoading = false, query = { repo.query() }, fetch = { val data = repo.fetch() @@ -143,7 +144,6 @@ class ResourceTest { networkBoundResource( isResultEmpty = { false }, mutex = saveResultMutex, - showSavedOnLoading = false, query = { repo.query() }, fetch = { val data = repo.fetch() From 961bc24f2799355638ab839cbd72b31b3db2bdb4 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 13 Mar 2024 19:13:56 +0100 Subject: [PATCH 444/545] Add docs to Resource, changing networkBoundResource generics naming (#2483) --- app/play-publish-lint.sh | 3 +- .../java/io/github/wulkanowy/data/Resource.kt | 51 +++++++++++++------ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/app/play-publish-lint.sh b/app/play-publish-lint.sh index d3354b1ad..5f0391de3 100755 --- a/app/play-publish-lint.sh +++ b/app/play-publish-lint.sh @@ -1,7 +1,8 @@ #!/bin/bash - content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit -if [[ "${#content}" -gt 500 ]]; then +content2=echo "$content" | dos2unix +if [[ "${#content2}" -gt 500 ]]; then echo >&2 "Release notes content has reached the limit of 500 characters" exit 1 fi 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 7c6c2a9ff..712a946f3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -24,13 +24,34 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds sealed interface Resource { - + /** + * The initial value of a resource flow. Indicates no data that is currently available to be shown, + * however with the expectation that the state will transition to another one soon. + */ open class Loading : Resource + /** + * A semi-loading state with some data available to be displayed (usually cached data loaded from + * the database). Still not the target state and it's expected to transition into another one soon. + */ data class Intermediate(val data: T) : Loading() + /** + * The happy-path target state. Data can either be: + * - loaded from the database - while it may seem like this case is already handled by the + * Intermediate state, the difference here is semantic. Cached data is returned as Intermediate + * when there's a API request in progress (or soon expected to be), however when there is no + * intention of immediately querying the API, the cached data is returned as a Success. + * - fetched from the API. + */ data class Success(val data: T) : Resource + /** + * Something bad happened and we were unable to get the requested data. This can be caused by + * a database error, a network error, or really just any other error. Upon receiving this state + * the UI can either: display a full screen error, or, when it has received any data previously, + * display a snack bar informing of the problem. + */ data class Error(val error: Throwable) : Resource } @@ -215,14 +236,14 @@ fun Flow>.debounceIntermediates(timeout: Duration = 5.seconds) = } -inline fun networkBoundResource( +inline fun networkBoundResource( mutex: Mutex = Mutex(), - crossinline isResultEmpty: (ResultType) -> Boolean, - crossinline query: () -> Flow, - crossinline fetch: suspend () -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline filterResult: (ResultType) -> ResultType = { it } + crossinline isResultEmpty: (OutputType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend () -> ApiType, + crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit, + crossinline shouldFetch: (OutputType) -> Boolean = { true }, + crossinline filterResult: (OutputType) -> OutputType = { it } ) = networkBoundResource( mutex = mutex, isResultEmpty = isResultEmpty, @@ -234,14 +255,14 @@ inline fun networkBoundResource( ) @JvmName("networkBoundResourceWithMap") -inline fun networkBoundResource( +inline fun networkBoundResource( mutex: Mutex = Mutex(), - crossinline isResultEmpty: (MappedResultType) -> Boolean, - crossinline query: () -> Flow, - crossinline fetch: suspend () -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline mapResult: (ResultType) -> MappedResultType, + crossinline isResultEmpty: (OutputType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend () -> ApiType, + crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit, + crossinline shouldFetch: (DatabaseType) -> Boolean = { true }, + crossinline mapResult: (DatabaseType) -> OutputType, ) = flow { emit(Resource.Loading()) From 34d34a050afc1ce81ef465c8ef47e04368484695 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:06:40 +0100 Subject: [PATCH 445/545] Add widget updating on data sync (#2487) --------- Co-authored-by: Faierbel --- .../repositories/LuckyNumberRepository.kt | 7 ++++ .../data/repositories/TimetableRepository.kt | 10 +++++- .../LuckyNumberWidgetProvider.kt | 6 +++- .../timetablewidget/TimetableWidgetFactory.kt | 9 ++++- .../wulkanowy/utils/AppWidgetUpdater.kt | 34 +++++++++++++++++++ .../repositories/LuckyNumberRemoteTest.kt | 7 +++- .../repositories/TimetableRepositoryTest.kt | 7 +++- 7 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 3636cb51e..dfafe6c8a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -6,6 +6,8 @@ import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource +import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider +import io.github.wulkanowy.utils.AppWidgetUpdater import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex @@ -18,6 +20,7 @@ import javax.inject.Singleton class LuckyNumberRepository @Inject constructor( private val luckyNumberDb: LuckyNumberDao, private val wulkanowySdkFactory: WulkanowySdkFactory, + private val appWidgetUpdater: AppWidgetUpdater, ) { private val saveFetchResultMutex = Mutex() @@ -26,6 +29,7 @@ class LuckyNumberRepository @Inject constructor( student: Student, forceRefresh: Boolean, notify: Boolean = false, + isFromAppWidget: Boolean = false ) = networkBoundResource( mutex = saveFetchResultMutex, isResultEmpty = { it == null }, @@ -44,6 +48,9 @@ class LuckyNumberRepository @Inject constructor( oldItems = listOfNotNull(oldLuckyNumber), newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }), ) + if (!isFromAppWidget) { + appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class) + } } } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 335789991..60c562e12 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -13,6 +13,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider +import io.github.wulkanowy.utils.AppWidgetUpdater import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.monday @@ -26,6 +28,7 @@ import java.time.LocalDate import javax.inject.Inject import javax.inject.Singleton + @Singleton class TimetableRepository @Inject constructor( private val timetableDb: TimetableDao, @@ -34,6 +37,7 @@ class TimetableRepository @Inject constructor( private val wulkanowySdkFactory: WulkanowySdkFactory, private val schedulerHelper: TimetableNotificationSchedulerHelper, private val refreshHelper: AutoRefreshHelper, + private val appWidgetUpdater: AppWidgetUpdater, ) { private val saveFetchResultMutex = Mutex() @@ -52,7 +56,8 @@ class TimetableRepository @Inject constructor( forceRefresh: Boolean, refreshAdditional: Boolean = false, notify: Boolean = false, - timetableType: TimetableType = TimetableType.NORMAL + timetableType: TimetableType = TimetableType.NORMAL, + isFromAppWidget: Boolean = false ) = networkBoundResource( mutex = saveFetchResultMutex, isResultEmpty = { @@ -83,6 +88,9 @@ class TimetableRepository @Inject constructor( refreshDayHeaders(timetableOld.headers, timetableNew.headers) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) + if (!isFromAppWidget) { + appWidgetUpdater.updateAllAppWidgetsByProvider(TimetableWidgetProvider::class) + } }, filterResult = { (timetable, additional, headers) -> TimetableFull( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index 1ab079a3a..e6de17818 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -145,7 +145,11 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { } if (currentStudent != null) { - luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false) + luckyNumberRepository.getLuckyNumber( + student = currentStudent, + forceRefresh = false, + isFromAppWidget = true + ) .toFirstResult() .dataOrThrow } else null 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 4cfc03229..1eb33dbcd 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 @@ -104,7 +104,14 @@ class TimetableWidgetFactory( private suspend fun getLessons( student: Student, semester: Semester, date: LocalDate ): List { - val timetable = timetableRepository.getTimetable(student, semester, date, date, false) + val timetable = timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = false, + isFromAppWidget = true + ) val lessons = timetable.toFirstResult().dataOrThrow.lessons return lessons.sortedBy { it.number } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt new file mode 100644 index 000000000..1b54f40c1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AppWidgetUpdater.kt @@ -0,0 +1,34 @@ +package io.github.wulkanowy.utils + +import android.appwidget.AppWidgetManager +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber +import javax.inject.Inject +import kotlin.reflect.KClass + +class AppWidgetUpdater @Inject constructor( + @ApplicationContext private val context: Context, + private val appWidgetManager: AppWidgetManager +) { + + fun updateAllAppWidgetsByProvider(providerClass: KClass) { + try { + val ids = appWidgetManager.getAppWidgetIds(ComponentName(context, providerClass.java)) + if (ids.isEmpty()) return + + val intent = Intent(context, providerClass.java) + .apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) + } + + context.sendBroadcast(intent) + } catch (e: Exception) { + Timber.e(e, "Failed to update all widgets for provider $providerClass") + } + } +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index 854d5d548..560eb3e7b 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -8,6 +8,7 @@ import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.AppWidgetUpdater import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery @@ -32,6 +33,9 @@ class LuckyNumberRemoteTest { @MockK private lateinit var luckyNumberDb: LuckyNumberDao + @MockK(relaxed = true) + private lateinit var appWidgetUpdater: AppWidgetUpdater + private val student = getStudentEntity() private lateinit var luckyNumberRepository: LuckyNumberRepository @@ -44,7 +48,8 @@ class LuckyNumberRemoteTest { fun setUp() { MockKAnnotations.init(this) - luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, wulkanowySdkFactory) + luckyNumberRepository = + LuckyNumberRepository(luckyNumberDb, wulkanowySdkFactory, appWidgetUpdater) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index a14605435..2a45f1755 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -12,6 +12,7 @@ import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.utils.AppWidgetUpdater import io.github.wulkanowy.utils.AutoRefreshHelper import io.mockk.MockKAnnotations import io.mockk.Runs @@ -53,6 +54,9 @@ class TimetableRepositoryTest { @MockK(relaxUnitFun = true) private lateinit var refreshHelper: AutoRefreshHelper + @MockK(relaxed = true) + private lateinit var appWidgetUpdater: AppWidgetUpdater + private val student = getStudentEntity() private val semester = getSemesterEntity() @@ -74,7 +78,8 @@ class TimetableRepositoryTest { timetableHeaderDao, wulkanowySdkFactory, timetableNotificationSchedulerHelper, - refreshHelper + refreshHelper, + appWidgetUpdater ) } From d01fe9c370720f3baba52701b3e467d603d81cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 19 Mar 2024 22:11:02 +0100 Subject: [PATCH 446/545] Bump sdk to 2.5.2-SNAPSHOT --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 31de5104a..7ecfc818b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.1' + implementation 'io.github.wulkanowy:sdk:2.5.2-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From b31c7e1720552c340fca3cf58b5073a9f82926ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:44:59 +0100 Subject: [PATCH 447/545] Fix task description color crash (#2475) --- .gitignore | 4 ++++ .../io/github/wulkanowy/ui/base/BaseActivity.kt | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 980085e38..69d3ad5d2 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,10 @@ captures/ .idea/discord.xml .idea/migrations.xml .idea/androidTestResultsUserPreferences.xml +.idea/copilot +.idea/deploymentTargetDropDown.xml +.idea/deploymentTargetSelector.xml +.idea/kotlinc.xml # Keystore files *.jks diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 10735dab3..922c35365 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.base import android.app.ActivityManager +import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast @@ -45,11 +46,19 @@ abstract class BaseActivity, VB : ViewBinding> : themeManager.applyActivityTheme(this) super.onCreate(savedInstanceState) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true) + applyCustomTaskDescription() + } - @Suppress("DEPRECATION") - setTaskDescription( - ActivityManager.TaskDescription(null, null, getThemeAttrColor(R.attr.colorSurface)) - ) + @Suppress("DEPRECATION") + private fun applyCustomTaskDescription() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) return + try { + val newColor = getThemeAttrColor(R.attr.colorSurface) + val taskDescription = ActivityManager.TaskDescription(null, null, newColor) + setTaskDescription(taskDescription) + } catch (e: Exception) { + Timber.e(e) + } } override fun showError(text: String, error: Throwable) { From 377c288e9ef7a4ea956318161c5641b65b79c358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:45:15 +0100 Subject: [PATCH 448/545] Add missing onDetachView in AutDialog (#2476) --- .../java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt index fa29df473..0f7c4234e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthDialog.kt @@ -78,4 +78,9 @@ class AuthDialog : BaseDialogFragment(), AuthView { override fun showDescriptionWithName(name: String) { binding.authDescription.text = getString(R.string.auth_description, name).parseAsHtml() } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } } From 78e28ad791b7aa23234362f931f5d7577d6f6e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 11:45:28 +0100 Subject: [PATCH 449/545] Remove savedInstance in MessagePreviewFragment (#2477) --- .../mailboxchooser/MailboxChooserDialog.kt | 1 - .../message/preview/MessagePreviewFragment.kt | 11 +++-------- .../message/preview/MessagePreviewPresenter.kt | 13 +++++++++---- .../github/wulkanowy/utils/BundleExtension.kt | 17 +++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt index 8bd84f2bf..11d3c6c12 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -47,7 +47,6 @@ class MailboxChooserDialog : BaseDialogFragment(), } - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView( 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 ebdb96a40..8e7c72765 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 @@ -82,10 +82,10 @@ class MessagePreviewFragment : get() = getString(R.string.message_not_exists) companion object { - const val MESSAGE_ID_KEY = "message_id" + private const val MESSAGE_ARG_KEY = "message" fun newInstance(message: Message) = MessagePreviewFragment().apply { - arguments = bundleOf(MESSAGE_ID_KEY to message) + arguments = bundleOf(MESSAGE_ARG_KEY to message) } } @@ -101,7 +101,7 @@ class MessagePreviewFragment : messageContainer = binding.messagePreviewContainer presenter.onAttachView( view = this, - message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY), + message = requireArguments().serializable(MESSAGE_ARG_KEY), ) } @@ -233,11 +233,6 @@ class MessagePreviewFragment : (activity as MainActivity).popView() } - override fun onSaveInstanceState(outState: Bundle) { - outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments?.message) - super.onSaveInstanceState(outState) - } - override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() 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 9bb0d32a4..3b3b2b420 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 @@ -3,10 +3,15 @@ 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.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder +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.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository @@ -28,17 +33,17 @@ class MessagePreviewPresenter @Inject constructor( private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { - var messageWithAttachments: MessageWithAttachment? = null + private var messageWithAttachments: MessageWithAttachment? = null private lateinit var lastError: Throwable private var retryCallback: () -> Unit = {} - fun onAttachView(view: MessagePreviewView, message: Message?) { + fun onAttachView(view: MessagePreviewView, message: Message) { super.onAttachView(view) view.initView() errorHandler.showErrorMessage = ::showErrorViewOnError - loadData(requireNotNull(message)) + loadData(message) } private fun onMessageLoadRetry(message: Message) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt index d3c9f8006..b1742b4fa 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/BundleExtension.kt @@ -4,30 +4,31 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.os.Parcelable +import androidx.core.os.BundleCompat import java.io.Serializable +// Even though API was introduced in 33, we use 34 as 33 is bugged in some scenarios. + inline fun Bundle.serializable(key: String): T = when { - Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!! + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java)!! else -> @Suppress("DEPRECATION") getSerializable(key) as T } inline fun Bundle.nullableSerializable(key: String): T? = when { - Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java) + Build.VERSION.SDK_INT >= 34 -> getSerializable(key, T::class.java) else -> @Suppress("DEPRECATION") getSerializable(key) as T? } @Suppress("UNCHECKED_CAST") -inline fun Bundle.parcelableArray(key: String): Array? = when { - Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java) - else -> @Suppress("DEPRECATION") getParcelableArray(key) as Array? -} +inline fun Bundle.parcelableArray(key: String): Array? = + BundleCompat.getParcelableArray(this, key, T::class.java) as Array? inline fun Intent.serializable(key: String): T = when { - Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!! + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java)!! else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T } inline fun Intent.nullableSerializable(key: String): T? = when { - Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java) + Build.VERSION.SDK_INT >= 34 -> getSerializableExtra(key, T::class.java) else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T? } From 8623b53357be1dff37ce77f6fa3b672cb24bff1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 23:38:17 +0100 Subject: [PATCH 450/545] Fix lateness color in attendance (#2481) --- .../modules/attendance/AttendanceAdapter.kt | 25 +++++++++++-------- app/src/main/res/values/colors.xml | 5 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 4e9baac3a..f5689ec8d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance +import android.content.res.ColorStateList import android.graphics.Typeface import android.view.LayoutInflater import android.view.View @@ -33,17 +34,17 @@ class AttendanceAdapter @Inject constructor() : ) override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val context = holder.binding.root.context val item = items[position] with(holder.binding) { attendanceItemNumber.text = item.number.toString() - attendanceItemSubject.text = item.subject.ifBlank { - root.context.getString(R.string.all_no_data) - } + attendanceItemSubject.text = item.subject + .ifBlank { context.getString(R.string.all_no_data) } attendanceItemDescription.setText(item.descriptionRes) attendanceItemDescription.setTextColor( - root.context.getThemeAttrColor( + context.getThemeAttrColor( when { item.absence && !item.excused -> R.attr.colorAttendanceAbsence item.lateness && !item.excused -> R.attr.colorAttendanceLateness @@ -61,13 +62,15 @@ class AttendanceAdapter @Inject constructor() : attendanceItemAlert.isVisible = item.let { (it.absence && !it.excused) || (it.lateness && !it.excused) } - attendanceItemAlert.setColorFilter(root.context.getThemeAttrColor( - when{ - item.absence && !item.excused -> R.attr.colorAttendanceAbsence - item.lateness && !item.excused -> R.attr.colorAttendanceLateness - else -> android.R.attr.colorPrimary - } - )) + attendanceItemAlert.imageTintList = ColorStateList.valueOf( + context.getThemeAttrColor( + when { + item.absence && !item.excused -> R.attr.colorAttendanceAbsence + item.lateness && !item.excused -> R.attr.colorAttendanceLateness + else -> android.R.attr.colorPrimary + } + ) + ) attendanceItemNumber.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.GONE diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8ad27ad88..f31a5f947 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -46,14 +46,15 @@ #d32f2f #e57373 + #ff8f00 #ffd54f #d32f2f #e57373 - #cd2a01 - #f05d0e + #ff8f00 + #ffd54f #1f000000 #1fffffff From d789aa718e8fda3bec328334812f456dacd15bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 20 Mar 2024 01:49:55 +0100 Subject: [PATCH 451/545] Change AuthDialog condition to isAuth flag (#2495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../62.json | 2547 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 5 +- .../wulkanowy/data/db/dao/StudentDao.kt | 4 + .../wulkanowy/data/db/entities/Student.kt | 7 + .../data/db/entities/StudentIsAuthorized.kt | 16 + .../data/mappers/RegisterUserMapper.kt | 24 +- .../wulkanowy/data/pojos/RegisterUser.kt | 2 + .../data/repositories/StudentRepository.kt | 24 + .../github/wulkanowy/ui/base/ErrorHandler.kt | 4 +- .../ui/modules/main/MainPresenter.kt | 10 + .../io/github/wulkanowy/utils/SdkExtension.kt | 1 + .../io/github/wulkanowy/TestEnityCreator.kt | 4 +- .../domain/GetMailboxByStudentUseCaseTest.kt | 2 + .../modules/grade/GradeAverageProviderTest.kt | 4 +- .../LoginStudentSelectPresenterTest.kt | 2 + .../ui/modules/main/MainPresenterTest.kt | 12 +- 16 files changed, 2651 insertions(+), 17 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json new file mode 100644 index 000000000..ab63c679f --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/62.json @@ -0,0 +1,2547 @@ +{ + "formatVersion": 1, + "database": { + "version": 62, + "identityHash": "ee2464d218b254ca868667c0fc756c0b", + "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, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER NOT NULL DEFAULT 0, `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": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "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, `author` TEXT, `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": "author", + "columnName": "author", + "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": "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, 'ee2464d218b254ca868667c0fc756c0b')" + ] + } +} \ 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 208daf75f..3189fc312 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 @@ -174,6 +174,7 @@ import javax.inject.Singleton AutoMigration(from = 58, to = 59), AutoMigration(from = 59, to = 60), AutoMigration(from = 60, to = 61), + AutoMigration(from = 61, to = 62), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -182,7 +183,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 61 + const val VERSION_SCHEMA = 62 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -309,6 +310,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val adminMessagesDao: AdminMessageDao abstract val mutedMessageSendersDao: MutedMessageSendersDao - + abstract val gradeDescriptiveDao: GradeDescriptiveDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index d9326ff6c..95abb399b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -9,6 +9,7 @@ import androidx.room.Transaction import androidx.room.Update import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsAuthorized import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import javax.inject.Singleton @@ -23,6 +24,9 @@ abstract class StudentDao { @Delete abstract suspend fun delete(student: Student) + @Update(entity = Student::class) + abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized) + @Update(entity = Student::class) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) 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 e1116733c..23e4439aa 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 @@ -78,6 +78,13 @@ data class Student( @ColumnInfo(name = "registration_date") val registrationDate: Instant, + + @ColumnInfo(name = "is_authorized", defaultValue = "0") + val isAuthorized: Boolean, + + @ColumnInfo(name = "is_edu_one", defaultValue = "0") + val isEduOne: Boolean, + ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt new file mode 100644 index 000000000..a1290177c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity +data class StudentIsAuthorized( + @ColumnInfo(name = "is_authorized", defaultValue = "0") + val isAuthorized: Boolean, +) : Serializable { + + @PrimaryKey + var id: Long = 0 +} 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 72c4861c9..7e6a8166d 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 @@ -34,17 +34,19 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser( error = it.error, students = it.subjects .filterIsInstance() - .map { registerSubject -> + .map { registerStudent -> RegisterStudent( - studentId = registerSubject.studentId, - studentName = registerSubject.studentName, - studentSecondName = registerSubject.studentSecondName, - studentSurname = registerSubject.studentSurname, - className = registerSubject.className, - classId = registerSubject.classId, - isParent = registerSubject.isParent, - semesters = registerSubject.semesters - .mapToEntities(registerSubject.studentId), + studentId = registerStudent.studentId, + studentName = registerStudent.studentName, + studentSecondName = registerStudent.studentSecondName, + studentSurname = registerStudent.studentSurname, + className = registerStudent.className, + classId = registerStudent.classId, + isParent = registerStudent.isParent, + isAuthorized = registerStudent.isAuthorized, + isEduOne = registerStudent.isEduOne, + semesters = registerStudent.semesters + .mapToEntities(registerStudent.studentId), ) }, ) @@ -84,6 +86,8 @@ fun RegisterStudent.mapToStudentWithSemesters( password = user.password.orEmpty(), isCurrent = false, registrationDate = Instant.now(), + isAuthorized = this.isAuthorized, + isEduOne = this.isEduOne, ).apply { avatarColor = colors.random() }, diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt index 98bf1402b..dec6ebec1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/RegisterUser.kt @@ -45,4 +45,6 @@ data class RegisterStudent( val classId: Int, val isParent: Boolean, val semesters: List, + val isAuthorized: Boolean, + val isEduOne: Boolean ) : java.io.Serializable 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 e063840cb..353e7ddd2 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 @@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsAuthorized import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters @@ -14,6 +15,7 @@ import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.getCurrentOrLast import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.security.Scrambler import io.github.wulkanowy.utils.switchSemester @@ -100,6 +102,25 @@ class StudentRepository @Inject constructor( return student } + suspend fun checkCurrentStudentAuthorizationStatus() { + val student = getCurrentStudent() + + if (!student.isAuthorized) { + val currentSemester = semesterDb.loadAll( + studentId = student.studentId, + classId = student.classId, + ).getCurrentOrLast() + val initializedSdk = sdk.init(student).switchSemester(currentSemester) + val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false + + if (isAuthorized) { + studentDb.update(StudentIsAuthorized(isAuthorized = true).apply { + id = student.id + }) + } else throw NoAuthorizationException() + } + } + suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException() @@ -176,3 +197,6 @@ class StudentRepository @Inject constructor( } } } + +class NoAuthorizationException : Exception() + diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index 7109f1ffd..00a2ab225 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.base import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException +import io.github.wulkanowy.data.repositories.NoAuthorizationException import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException @@ -40,7 +40,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co is ScramblerException -> onDecryptionFailed() is BadCredentialsException -> onExpiredCredentials() is NoCurrentStudentException -> onNoCurrentStudent() - is AuthorizationRequiredException -> onAuthorizationRequired() + is NoAuthorizationException -> onAuthorizationRequired() is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 5469fcad3..67f729afb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -73,6 +73,7 @@ class MainPresenter @Inject constructor( syncManager.startPeriodicSyncWorker() checkAppSupport() + checkCurrentStudentAuthorizationStatus() analytics.logEvent("app_open", "destination" to initDestination.toString()) Timber.i("Main view was initialized with $initDestination") @@ -191,4 +192,13 @@ class MainPresenter @Inject constructor( view?.showStudentAvatar(currentStudent) } + + private fun checkCurrentStudentAuthorizationStatus() { + presenterScope.launch { + runCatching { studentRepository.checkCurrentStudentAuthorizationStatus() } + .onFailure { errorHandler.dispatch(it) } + + Timber.i("Current student authorization status checked") + } + } } 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 9b6ca7060..77f03fb88 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -12,6 +12,7 @@ fun Sdk.init(student: Student): Sdk { schoolSymbol = student.schoolSymbol studentId = student.studentId classId = student.classId + isEduOne = student.isEduOne emptyCookieJarInterceptor = true if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index 9f5d731b6..96b75c35d 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -5,8 +5,8 @@ import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk -import java.time.LocalDate import java.time.Instant.now +import java.time.LocalDate import io.github.wulkanowy.sdk.pojo.Semester as SdkSemester fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate = LocalDate.now(), end: LocalDate = LocalDate.now(), semesterName: Int = 1) = Semester( @@ -72,6 +72,8 @@ fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.HEBE) = Student( symbol = "", userLoginId = 1, userName = "", + isEduOne = false, + isAuthorized = false ).apply { id = 1 } 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 34a8fe991..2a63c6b80 100644 --- a/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt +++ b/app/src/test/java/io/github/wulkanowy/domain/GetMailboxByStudentUseCaseTest.kt @@ -222,5 +222,7 @@ class GetMailboxByStudentUseCaseTest { symbol = "", userLoginId = 1, userName = userName, + isAuthorized = false, + isEduOne = false ) } 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 4f0f80fe1..ad7bbe15f 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 @@ -72,7 +72,9 @@ class GradeAverageProviderTest { className = "", classId = 1, isCurrent = true, - registrationDate = Instant.now() + registrationDate = Instant.now(), + isAuthorized = false, + isEduOne = false ) private val semesters = mutableListOf( 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 34965f00d..d03337dc3 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 @@ -71,6 +71,8 @@ class LoginStudentSelectPresenterTest { classId = 0, isParent = false, semesters = listOf(), + isEduOne = false, + isAuthorized = false, ) private val school = RegisterUnit( diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 460c8385f..3e95774dd 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.main +import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager @@ -7,14 +8,23 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.clearMocks +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify import kotlinx.serialization.json.Json import org.junit.Before +import org.junit.Rule import org.junit.Test class MainPresenterTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + @MockK(relaxed = true) lateinit var errorHandler: ErrorHandler From 6047af9ff0c18b7e291cf2fe9ff551ae0d5eefea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 20 Mar 2024 02:18:00 +0100 Subject: [PATCH 452/545] Version 2.5.2 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 12 ++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7ecfc818b..e73784a55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 150 - versionName "2.5.1" + versionCode 151 + versionName "2.5.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -165,7 +165,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.50d - updatePriority = 1 + updatePriority = 3 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.2-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.2' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 2a57977f1..6eafb6a99 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,11 +1,7 @@ -Wersja 2.5.1 +Wersja 2.5.2 -— dodaliśmy wyświetlanie ogłoszeń -— dodaliśmy opcję przywracania wiadomości z kosza -— dodaliśmy opcję wyciszania nadawców wiadomości -— naprawiliśmy opcjonalne liczenie średniej arytmetycznej, kiedy brak ocen z wagą w drugim semestrze -— usprawniliśmy ładowanie frekwencji i planu lekcji -— naprawiliśmy usprawiedliwianie nieobecności i autoryzację u użytkowników eduOne -— zmieniliśmy komunikat o zmienionym haśle +— naprawiliśmy omyłkowe wyświetlanie ekranu z wymaganą autoryzacją numerem PESEL +— naprawiliśmy kilka problemów ze stabilnością +— poprawiliśmy wyświetlanie kolorów we frekwencji Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From c9a42a6cf634c1225a922fb8a50cbc8c776e8943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 21 Mar 2024 11:03:08 +0100 Subject: [PATCH 453/545] Add try catch to initialize MobileAds SDK (#2497) --- app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt index bd17d52c1..a873c99ef 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AdsHelper.kt @@ -93,8 +93,12 @@ class AdsHelper @Inject constructor( private fun initializeMobileAds() { if (isMobileAdsInitializeCalled.getAndSet(true)) return - MobileAds.initialize(context) { - isMobileAdsSdkInitialized.value = true + try { + MobileAds.initialize(context) { + isMobileAdsSdkInitialized.value = true + } + } catch (e: Exception) { + Timber.e(e) } } From 5a77d1e94066457e65028ac4a807606e67b1d80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 21 Mar 2024 21:19:49 +0100 Subject: [PATCH 454/545] Hide lesson number when is eduOne (#2498) --- .../ui/modules/timetable/TimetableAdapter.kt | 4 +++- .../wulkanowy/ui/modules/timetable/TimetableItem.kt | 2 ++ .../ui/modules/timetable/TimetablePresenter.kt | 13 ++++++++----- .../timetablewidget/TimetableWidgetFactory.kt | 12 ++++++------ .../modules/timetablewidget/TimetableWidgetItem.kt | 1 + 5 files changed, 20 insertions(+), 12 deletions(-) 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 a957ef798..a4221a2a2 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 @@ -27,7 +27,7 @@ class TimetableAdapter @Inject constructor() : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (TimetableItemType.values()[viewType]) { + return when (TimetableItemType.entries[viewType]) { TimetableItemType.SMALL -> SmallViewHolder( ItemTimetableSmallBinding.inflate(inflater, parent, false) ) @@ -79,6 +79,7 @@ class TimetableAdapter @Inject constructor() : with(binding) { timetableSmallItemNumber.text = lesson.number.toString() + timetableSmallItemNumber.isVisible = item.isLessonNumberVisible timetableSmallItemSubject.text = lesson.subject timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm") timetableSmallItemRoom.text = lesson.room @@ -97,6 +98,7 @@ class TimetableAdapter @Inject constructor() : with(binding) { timetableItemNumber.text = lesson.number.toString() + timetableItemNumber.isVisible = item.isLessonNumberVisible timetableItemSubject.text = lesson.subject timetableItemGroup.text = lesson.group timetableItemRoom.text = lesson.room 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 105ece38c..402b03dd9 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 @@ -7,12 +7,14 @@ sealed class TimetableItem(val type: TimetableItemType) { data class Small( val lesson: Timetable, + val isLessonNumberVisible: Boolean, val onClick: (Timetable) -> Unit, ) : TimetableItem(TimetableItemType.SMALL) data class Normal( val lesson: Timetable, val showGroupsInPlan: Boolean, + val isLessonNumberVisible: Boolean, val timeLeft: TimeLeft?, val onClick: (Timetable) -> Unit, ) : TimetableItem(TimetableItemType.NORMAL) 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 e83f25176..abaf7d86b 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 @@ -57,6 +57,7 @@ class TimetablePresenter @Inject constructor( private var initialDate: LocalDate? = null private var isWeekendHasLessons: Boolean = false + private var isEduOne: Boolean = false var currentDate: LocalDate? = null private set @@ -149,6 +150,7 @@ class TimetablePresenter @Inject constructor( val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) + isEduOne = student.isEduOne checkInitialAndCurrentDate(semester) timetableRepository.getTimetable( student = student, @@ -234,9 +236,8 @@ class TimetablePresenter @Inject constructor( if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { it.isStudentPlan } else true - }.sortedWith( - compareBy({ item -> item.number }, { item -> !item.isStudentPlan }) - ) + } + .sortedWith(compareBy({ item -> item.start }, { item -> !item.isStudentPlan })) var prevNum = when (prefRepository.showTimetableGaps) { BETWEEN_AND_BEFORE_LESSONS -> 0 @@ -257,13 +258,15 @@ class TimetablePresenter @Inject constructor( lesson = it, showGroupsInPlan = prefRepository.showGroupsInPlan, timeLeft = filteredItems.getTimeLeftForLesson(it, i), - onClick = ::onTimetableItemSelected + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne ) add(normalLesson) } else { val smallLesson = TimetableItem.Small( lesson = it, - onClick = ::onTimetableItemSelected + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne ) add(smallLesson) } 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 4cfc03229..5fb44a5e7 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 @@ -46,11 +46,8 @@ class TimetableWidgetFactory( ) : RemoteViewsService.RemoteViewsFactory { private var items = emptyList() - private var timetableCanceledColor: Int? = null - private var textColor: Int? = null - private var timetableChangeColor: Int? = null override fun getLoadingView() = null @@ -81,7 +78,7 @@ class TimetableWidgetFactory( val lessons = getLessons(student, semester, date) val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) - createItems(lessons, lastSync) + createItems(lessons, lastSync, !student.isEduOne) } .onFailure { items = listOf(TimetableWidgetItem.Error(it)) @@ -106,12 +103,13 @@ class TimetableWidgetFactory( ): List { val timetable = timetableRepository.getTimetable(student, semester, date, date, false) val lessons = timetable.toFirstResult().dataOrThrow.lessons - return lessons.sortedBy { it.number } + return lessons.sortedBy { it.start } } private fun createItems( lessons: List, lastSync: Instant?, + isEduOne: Boolean ): List { var prevNum = when (prefRepository.showTimetableGaps) { BETWEEN_AND_BEFORE_LESSONS -> 0 @@ -127,7 +125,7 @@ class TimetableWidgetFactory( ) add(emptyItem) } - add(TimetableWidgetItem.Normal(it)) + add(TimetableWidgetItem.Normal(it, isEduOne)) prevNum = it.number } add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN)) @@ -155,9 +153,11 @@ class TimetableWidgetFactory( val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE) val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE) + val lessonNumberVisibility = if (item.isLessonNumberVisible) VISIBLE else GONE val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) + setViewVisibility(R.id.timetableWidgetItemNumber, lessonNumberVisibility) setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime) setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime) setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) 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 index fb02f8919..d4c2cfc00 100644 --- 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 @@ -7,6 +7,7 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) { data class Normal( val lesson: Timetable, + val isLessonNumberVisible: Boolean, ) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL) data class Empty( From 0554aa91fd04bd489b93b634849cf9164f2a7e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Mar 2024 23:38:39 +0100 Subject: [PATCH 455/545] Add WulkanowySdkFactory (#2479) --- .../io/github/wulkanowy/data/DataModule.kt | 21 ------ .../wulkanowy/data/WulkanowySdkFactory.kt | 65 +++++++++++++++++++ .../data/repositories/AttendanceRepository.kt | 18 +++-- .../AttendanceSummaryRepository.kt | 9 +-- .../CompletedLessonsRepository.kt | 9 +-- .../data/repositories/ConferenceRepository.kt | 9 +-- .../data/repositories/ExamRepository.kt | 9 +-- .../data/repositories/GradeRepository.kt | 9 +-- .../repositories/GradeStatisticsRepository.kt | 15 ++--- .../data/repositories/HomeworkRepository.kt | 9 +-- .../repositories/LuckyNumberRepository.kt | 9 +-- .../data/repositories/MessageRepository.kt | 59 ++++++++++------- .../repositories/MobileDeviceRepository.kt | 15 ++--- .../data/repositories/NoteRepository.kt | 9 +-- .../data/repositories/RecipientRepository.kt | 10 +-- .../data/repositories/RecoverRepository.kt | 20 ++++-- .../SchoolAnnouncementRepository.kt | 7 +- .../data/repositories/SchoolRepository.kt | 9 +-- .../data/repositories/SchoolsRepository.kt | 11 ++-- .../data/repositories/SemesterRepository.kt | 9 ++- .../repositories/StudentInfoRepository.kt | 12 ++-- .../data/repositories/StudentRepository.kt | 19 +++--- .../data/repositories/SubjectRepository.kt | 9 +-- .../data/repositories/TeacherRepository.kt | 9 +-- .../data/repositories/TimetableRepository.kt | 9 +-- .../ui/modules/captcha/CaptchaDialog.kt | 6 +- .../io/github/wulkanowy/utils/SdkExtension.kt | 43 ------------ .../wulkanowy/WulkanowySdkFactoryCreator.kt | 12 ++++ .../repositories/AttendanceRepositoryTest.kt | 10 +-- .../CompletedLessonsRepositoryTest.kt | 9 +-- .../data/repositories/ExamRemoteTest.kt | 9 +-- .../data/repositories/GradeRepositoryTest.kt | 9 +-- .../GradeStatisticsRepositoryTest.kt | 16 +++-- .../repositories/LuckyNumberRemoteTest.kt | 9 +-- .../repositories/MessageRepositoryTest.kt | 11 ++-- .../MobileDeviceRepositoryTest.kt | 10 +-- .../data/repositories/RecipientLocalTest.kt | 16 +++-- .../repositories/SemesterRepositoryTest.kt | 10 +-- .../repositories/TimetableRepositoryTest.kt | 9 +-- 39 files changed, 284 insertions(+), 284 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt create mode 100644 app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index 50d6c8f9f..a492c08db 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -18,17 +18,13 @@ import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.RemoteConfigHelper -import io.github.wulkanowy.utils.WebkitCookieManagerProxy import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.create -import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -36,23 +32,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal class DataModule { - @Singleton - @Provides - fun provideSdk( - chuckerInterceptor: ChuckerInterceptor, - remoteConfig: RemoteConfigHelper, - webkitCookieManagerProxy: WebkitCookieManagerProxy, - ) = Sdk().apply { - androidVersion = android.os.Build.VERSION.RELEASE - buildTag = android.os.Build.MODEL - userAgentTemplate = remoteConfig.userAgentTemplate - setSimpleHttpLogger { Timber.d(it) } - setAdditionalCookieManager(webkitCookieManagerProxy) - - // for debug only - addInterceptor(chuckerInterceptor, network = true) - } - @Singleton @Provides fun provideChuckerCollector( diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt new file mode 100644 index 000000000..63890ea00 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -0,0 +1,65 @@ +package io.github.wulkanowy.data + +import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.RemoteConfigHelper +import io.github.wulkanowy.utils.WebkitCookieManagerProxy +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WulkanowySdkFactory @Inject constructor( + private val chuckerInterceptor: ChuckerInterceptor, + private val remoteConfig: RemoteConfigHelper, + private val webkitCookieManagerProxy: WebkitCookieManagerProxy +) { + + private val sdk = Sdk().apply { + androidVersion = android.os.Build.VERSION.RELEASE + buildTag = android.os.Build.MODEL + userAgentTemplate = remoteConfig.userAgentTemplate + setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(webkitCookieManagerProxy) + + // for debug only + addInterceptor(chuckerInterceptor, network = true) + } + + fun create() = sdk + + fun create(student: Student, semester: Semester? = null): Sdk { + return create().apply { + email = student.email + password = student.password + symbol = student.symbol + schoolSymbol = student.schoolSymbol + studentId = student.studentId + classId = student.classId + emptyCookieJarInterceptor = true + isEduOne = student.isEduOne + + if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { + mobileBaseUrl = student.mobileBaseUrl + } else { + scrapperBaseUrl = student.scrapperBaseUrl + domainSuffix = student.scrapperDomainSuffix + loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) + } + + mode = Sdk.Mode.valueOf(student.loginMode) + mobileBaseUrl = student.mobileBaseUrl + keyId = student.certificateKey + privatePem = student.privateKey + + if (semester != null) { + diaryId = semester.diaryId + kindergartenDiaryId = semester.kindergartenDiaryId + schoolYear = semester.schoolYear + unitId = semester.unitId + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 46ea29f83..9b94cc103 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance @@ -7,14 +8,11 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -28,7 +26,7 @@ import javax.inject.Singleton class AttendanceRepository @Inject constructor( private val attendanceDb: AttendanceDao, private val timetableDb: TimetableDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -59,8 +57,7 @@ class AttendanceRepository @Inject constructor( val lessons = timetableDb.load( semester.diaryId, semester.studentId, start.monday, end.sunday ) - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getAttendance(start.monday, end.sunday) .mapToEntities(semester, lessons) }, @@ -90,8 +87,10 @@ class AttendanceRepository @Inject constructor( } suspend fun excuseForAbsence( - student: Student, semester: Semester, - absenceList: List, reason: String? = null + student: Student, + semester: Semester, + absenceList: List, + reason: String? = null ) { val items = absenceList.map { attendance -> Absent( @@ -99,8 +98,7 @@ class AttendanceRepository @Inject constructor( timeId = attendance.timeId ) } - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .excuseForAbsence(items, reason) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index c6cfc2f6b..78c98169b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories import androidx.room.withTransaction +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -20,9 +18,9 @@ import javax.inject.Singleton @Singleton class AttendanceSummaryRepository @Inject constructor( private val attendanceDb: AttendanceSummaryDao, - private val sdk: Sdk, private val refreshHelper: AutoRefreshHelper, private val appDatabase: AppDatabase, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -43,8 +41,7 @@ class AttendanceSummaryRepository @Inject constructor( }, query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getAttendanceSummary(subjectId) .mapToEntities(semester, subjectId) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index f7f86b23d..45a36f55c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class CompletedLessonsRepository @Inject constructor( private val completedLessonsDb: CompletedLessonsDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -53,8 +51,7 @@ class CompletedLessonsRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getCompletedLessons(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index fbe578604..58ce0091a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -1,16 +1,14 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class ConferenceRepository @Inject constructor( private val conferenceDb: ConferenceDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -46,8 +44,7 @@ class ConferenceRepository @Inject constructor( conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getConferences() .mapToEntities(semester) .filter { it.date >= startDate } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 9b8dd02e3..89dbcd5ce 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -1,18 +1,16 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.endExamsDay import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.startExamsDay -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -23,7 +21,7 @@ import javax.inject.Singleton @Singleton class ExamRepository @Inject constructor( private val examDb: ExamDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -56,8 +54,7 @@ class ExamRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getExams(start.startExamsDay, start.endExamsDay) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index ac1ef541b..e899f900d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao @@ -10,11 +11,8 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.toLocalDate import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -30,7 +28,7 @@ class GradeRepository @Inject constructor( private val gradeDb: GradeDao, private val gradeSummaryDb: GradeSummaryDao, private val gradeDescriptiveDb: GradeDescriptiveDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -63,8 +61,7 @@ class GradeRepository @Inject constructor( } }, fetch = { - val (details, summary, descriptive) = sdk.init(student) - .switchSemester(semester) + val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester) .getGrades(semester.semesterId) Triple( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 809f92d3e..f120d34f3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao @@ -12,11 +13,8 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.util.Locale @@ -28,7 +26,7 @@ class GradeStatisticsRepository @Inject constructor( private val gradePartialStatisticsDb: GradePartialStatisticsDao, private val gradePointsStatisticsDb: GradePointsStatisticsDao, private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -56,8 +54,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesPartialStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -104,8 +101,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesSemesterStatistics(semester.semesterId) .mapToEntities(semester) }, @@ -163,8 +159,7 @@ class GradeStatisticsRepository @Inject constructor( }, query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getGradesPointsStatistics(semester.semesterId) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 1a9c7ffaf..7893ef631 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -1,18 +1,16 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.time.LocalDate @@ -22,7 +20,7 @@ import javax.inject.Singleton @Singleton class HomeworkRepository @Inject constructor( private val homeworkDb: HomeworkDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -55,8 +53,7 @@ class HomeworkRepository @Inject constructor( ) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getHomework(start.monday, end.sunday) .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 45b7f6e29..3636cb51e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -1,12 +1,11 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex @@ -18,7 +17,7 @@ import javax.inject.Singleton @Singleton class LuckyNumberRepository @Inject constructor( private val luckyNumberDb: LuckyNumberDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -33,7 +32,9 @@ class LuckyNumberRepository @Inject constructor( shouldFetch = { it == null || forceRefresh }, query = { luckyNumberDb.load(student.studentId, now()) }, fetch = { - sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) + wulkanowySdkFactory.create(student) + .getLuckyNumber(student.schoolShortName) + ?.mapToEntity(student) }, saveFetchResult = { oldLuckyNumber, newLuckyNumber -> newLuckyNumber ?: return@networkBoundResource 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 a4517760b..ede2a0fde 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 @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao @@ -29,11 +30,9 @@ import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.toFirstResult 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 import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -48,7 +47,7 @@ class MessageRepository @Inject constructor( private val messagesDb: MessagesDao, private val mutedMessageSendersDao: MutedMessageSendersDao, private val messageAttachmentDao: MessageAttachmentDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, @ApplicationContext private val context: Context, private val refreshHelper: AutoRefreshHelper, private val sharedPrefProvider: SharedPrefProvider, @@ -82,10 +81,16 @@ class MessageRepository @Inject constructor( } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id) }, fetch = { - sdk.init(student).getMessages( - folder = Folder.valueOf(folder.name), - mailboxKey = mailbox?.globalKey, - ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email)) + wulkanowySdkFactory.create(student) + .getMessages( + folder = Folder.valueOf(folder.name), + mailboxKey = mailbox?.globalKey, + ) + .mapToEntities( + student = student, + mailbox = mailbox, + allMailboxes = mailboxDao.loadAll(student.email) + ) }, saveFetchResult = { oldWithAuthors, new -> val old = oldWithAuthors.map { it.message } @@ -115,10 +120,11 @@ class MessageRepository @Inject constructor( }, query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, fetch = { - sdk.init(student).getMessageDetails( - messageKey = it!!.message.messageGlobalKey, - markAsRead = message.unread && markAsRead, - ) + wulkanowySdkFactory.create(student) + .getMessageDetails( + messageKey = it!!.message.messageGlobalKey, + markAsRead = message.unread && markAsRead, + ) }, saveFetchResult = { old, new -> checkNotNull(old) { "Fetched message no longer exist!" } @@ -159,19 +165,19 @@ class MessageRepository @Inject constructor( recipients: List, mailbox: Mailbox, ) { - sdk.init(student).sendMessage( - subject = subject, - content = content, - recipients = recipients.mapFromEntities(), - mailboxId = mailbox.globalKey, - ) + wulkanowySdkFactory.create(student) + .sendMessage( + subject = subject, + content = content, + recipients = recipients.mapFromEntities(), + mailboxId = mailbox.globalKey, + ) refreshFolders(student, mailbox, listOf(SENT)) } suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List) { - sdk.init(student).restoreMessages( - messages = messages.map { it.messageGlobalKey }, - ) + wulkanowySdkFactory.create(student) + .restoreMessages(messages = messages.map { it.messageGlobalKey }) refreshFolders(student, mailbox) } @@ -182,10 +188,11 @@ class MessageRepository @Inject constructor( suspend fun deleteMessages(student: Student, messages: List) { val firstMessage = messages.first() - sdk.init(student).deleteMessages( - messages = messages.map { it.messageGlobalKey }, - removeForever = firstMessage.folderId == TRASHED.id, - ) + wulkanowySdkFactory.create(student) + .deleteMessages( + messages = messages.map { it.messageGlobalKey }, + removeForever = firstMessage.folderId == TRASHED.id, + ) if (firstMessage.folderId != TRASHED.id) { val deletedMessages = messages.map { @@ -230,7 +237,9 @@ class MessageRepository @Inject constructor( }, query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, fetch = { - sdk.init(student).getMailboxes().mapToEntities(student) + wulkanowySdkFactory.create(student) + .getMailboxes() + .mapToEntities(student) }, saveFetchResult = { old, new -> mailboxDao.deleteAll(old uniqueSubtract new) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 48b4fc287..19466554a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.Semester @@ -8,11 +9,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class MobileDeviceRepository @Inject constructor( private val mobileDb: MobileDeviceDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -42,8 +40,7 @@ class MobileDeviceRepository @Inject constructor( }, query = { mobileDb.loadAll(student.userLoginId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getRegisteredDevices() .mapToEntities(student) }, @@ -57,16 +54,14 @@ class MobileDeviceRepository @Inject constructor( ) suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .unregisterDevice(device.deviceId) mobileDb.deleteAll(listOf(device)) } suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { - return sdk.init(student) - .switchSemester(semester) + return wulkanowySdkFactory.create(student, semester) .getToken() .mapToMobileDeviceToken() } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index feb92c154..9551e01eb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -1,16 +1,14 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.toLocalDate import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -21,7 +19,7 @@ import javax.inject.Singleton @Singleton class NoteRepository @Inject constructor( private val noteDb: NoteDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -45,8 +43,7 @@ class NoteRepository @Inject constructor( }, query = { noteDb.loadAll(student.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getNotes() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt index 4a1474ced..8233d932e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.MailboxType @@ -7,10 +8,8 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import javax.inject.Inject import javax.inject.Singleton @@ -18,14 +17,15 @@ import javax.inject.Singleton @Singleton class RecipientRepository @Inject constructor( private val recipientDb: RecipientDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { private val cacheKey = "recipient" suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) { - val new = sdk.init(student).getRecipients(mailbox.globalKey) + val new = wulkanowySdkFactory.create(student) + .getRecipients(mailbox.globalKey) .mapToEntities(mailbox.globalKey) val old = recipientDb.loadAll(type, mailbox.globalKey) @@ -60,7 +60,7 @@ class RecipientRepository @Inject constructor( ): List { mailbox ?: return emptyList() - return sdk.init(student) + return wulkanowySdkFactory.create(student) .getMessageReplayDetails(message.messageGlobalKey) .sender .let(::listOf) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt index 5940f477b..b554bda0f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt @@ -1,17 +1,23 @@ package io.github.wulkanowy.data.repositories -import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.data.WulkanowySdkFactory import javax.inject.Inject import javax.inject.Singleton @Singleton -class RecoverRepository @Inject constructor(private val sdk: Sdk) { +class RecoverRepository @Inject constructor( + private val wulkanowySdkFactory: WulkanowySdkFactory +) { - suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair { - return sdk.getPasswordResetCaptchaCode(host, symbol) - } + suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair = + wulkanowySdkFactory.create() + .getPasswordResetCaptchaCode(host, symbol) suspend fun sendRecoverRequest( - url: String, symbol: String, email: String, reCaptchaResponse: String - ): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) + url: String, + symbol: String, + email: String, + reCaptchaResponse: String + ): String = wulkanowySdkFactory.create() + .sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index f09a46aa1..6a04ce75f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -1,14 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -18,7 +17,7 @@ import javax.inject.Singleton @Singleton class SchoolAnnouncementRepository @Inject constructor( private val schoolAnnouncementDb: SchoolAnnouncementDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -41,7 +40,7 @@ class SchoolAnnouncementRepository @Inject constructor( schoolAnnouncementDb.loadAll(student.userLoginId) }, fetch = { - val sdk = sdk.init(student) + val sdk = wulkanowySdkFactory.create(student) val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student) val directorInformation = sdk.getDirectorInformation().mapToEntities(student) lastAnnouncements + directorInformation diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index b42b4d577..c48abb6f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -17,7 +15,7 @@ import javax.inject.Singleton @Singleton class SchoolRepository @Inject constructor( private val schoolDb: SchoolDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -40,8 +38,7 @@ class SchoolRepository @Inject constructor( }, query = { schoolDb.load(semester.studentId, semester.classId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getSchool() .mapToEntity(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt index 216a8c112..4a16d6f13 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolsRepository.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.pojos.IntegrityRequest import io.github.wulkanowy.data.pojos.LoginEvent -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.utils.IntegrityHelper import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withTimeout import timber.log.Timber import java.util.UUID @@ -23,7 +21,7 @@ import kotlin.time.Duration.Companion.seconds class SchoolsRepository @Inject constructor( private val integrityHelper: IntegrityHelper, private val schoolsService: SchoolsService, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { suspend fun logSchoolLogin(loginData: LoginData, students: List) { @@ -40,10 +38,9 @@ class SchoolsRepository @Inject constructor( private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) { val requestId = UUID.randomUUID().toString() val token = integrityHelper.getIntegrityToken(requestId) ?: return + val updatedStudent = student.copy(password = loginData.password) - val schoolInfo = sdk - .init(student.copy(password = loginData.password)) - .switchSemester(semester) + val schoolInfo = wulkanowySdkFactory.create(updatedStudent, semester) .getSchool() schoolsService.logLoginEvent( 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 9ae22babc..da21f59ac 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student @@ -7,7 +8,6 @@ import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.withContext @@ -18,7 +18,7 @@ import javax.inject.Singleton @Singleton class SemesterRepository @Inject constructor( private val semesterDb: SemesterDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val dispatchers: DispatchersProvider, ) { @@ -60,7 +60,10 @@ class SemesterRepository @Inject constructor( } private suspend fun refreshSemesters(student: Student) { - val new = sdk.init(student).getSemesters().mapToEntities(student.studentId) + val new = wulkanowySdkFactory.create(student) + .getSemesters() + .mapToEntities(student.studentId) + if (new.isEmpty()) return Timber.i("Empty semester list!") val old = semesterDb.loadAll(student.studentId, student.classId) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index d42be180d..db4c0aebb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -1,13 +1,11 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.StudentInfoDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -15,7 +13,7 @@ import javax.inject.Singleton @Singleton class StudentInfoRepository @Inject constructor( private val studentInfoDao: StudentInfoDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, ) { private val saveFetchResultMutex = Mutex() @@ -30,9 +28,9 @@ class StudentInfoRepository @Inject constructor( shouldFetch = { it == null || forceRefresh }, query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) - .getStudentInfo().mapToEntity(semester) + wulkanowySdkFactory.create(student, semester) + .getStudentInfo() + .mapToEntity(semester) }, saveFetchResult = { old, new -> if (old != null && new != old) { 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 353e7ddd2..a5fea4ecb 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import androidx.room.withTransaction +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao @@ -16,9 +17,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCurrentOrLast -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.security.Scrambler -import io.github.wulkanowy.utils.switchSemester import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -28,7 +27,7 @@ class StudentRepository @Inject constructor( private val dispatchers: DispatchersProvider, private val studentDb: StudentDao, private val semesterDb: SemesterDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val appDatabase: AppDatabase, private val scrambler: Scrambler, ) { @@ -39,7 +38,7 @@ class StudentRepository @Inject constructor( pin: String, symbol: String, token: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getStudentsFromHebe(token, pin, symbol, "") .mapToPojo(null) @@ -49,7 +48,7 @@ class StudentRepository @Inject constructor( scrapperBaseUrl: String, domainSuffix: String, symbol: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol) .mapToPojo(password) @@ -58,7 +57,7 @@ class StudentRepository @Inject constructor( password: String, scrapperBaseUrl: String, symbol: String - ): RegisterUser = sdk + ): RegisterUser = wulkanowySdkFactory.create() .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) .mapToPojo(password) @@ -110,7 +109,7 @@ class StudentRepository @Inject constructor( studentId = student.studentId, classId = student.classId, ).getCurrentOrLast() - val initializedSdk = sdk.init(student).switchSemester(currentSemester) + val initializedSdk = wulkanowySdkFactory.create(student, currentSemester) val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false if (isAuthorized) { @@ -170,13 +169,11 @@ class StudentRepository @Inject constructor( .distinctBy { it.student.studentName }.size == 1 suspend fun authorizePermission(student: Student, semester: Semester, pesel: String) = - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .authorizePermission(pesel) suspend fun refreshStudentName(student: Student, semester: Semester) { - val newCurrentApiStudent = sdk.init(student) - .switchSemester(semester) + val newCurrentApiStudent = wulkanowySdkFactory.create(student, semester) .getCurrentStudent() ?: return val studentName = StudentName( diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index cf7f86c22..573c7c149 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -18,7 +16,7 @@ import javax.inject.Singleton @Singleton class SubjectRepository @Inject constructor( private val subjectDao: SubjectDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -39,8 +37,7 @@ class SubjectRepository @Inject constructor( }, query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getSubjects() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 5a488b27c..a5a6e3f9c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -1,15 +1,13 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.TeacherDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -18,7 +16,7 @@ import javax.inject.Singleton @Singleton class TeacherRepository @Inject constructor( private val teacherDb: TeacherDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val refreshHelper: AutoRefreshHelper, ) { @@ -39,8 +37,7 @@ class TeacherRepository @Inject constructor( }, query = { teacherDb.loadAll(semester.studentId, semester.classId) }, fetch = { - sdk.init(student) - .switchSemester(semester) + wulkanowySdkFactory.create(student, semester) .getTeachers() .mapToEntities(semester) }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 0d208c1fc..335789991 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao @@ -11,14 +12,11 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey -import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -33,7 +31,7 @@ class TimetableRepository @Inject constructor( private val timetableDb: TimetableDao, private val timetableAdditionalDb: TimetableAdditionalDao, private val timetableHeaderDb: TimetableHeaderDao, - private val sdk: Sdk, + private val wulkanowySdkFactory: WulkanowySdkFactory, private val schedulerHelper: TimetableNotificationSchedulerHelper, private val refreshHelper: AutoRefreshHelper, ) { @@ -74,8 +72,7 @@ class TimetableRepository @Inject constructor( }, query = { getFullTimetableFromDatabase(student, semester, start, end) }, fetch = { - val timetableFull = sdk.init(student) - .switchSemester(semester) + val timetableFull = wulkanowySdkFactory.create(student, semester) .getTimetable(start.monday, end.sunday) timetableFull.mapToEntities(semester) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt index 98b4fda71..ce2173d28 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -10,8 +10,8 @@ import android.webkit.WebViewClient import androidx.core.os.bundleOf import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.databinding.DialogCaptchaBinding -import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.WebkitCookieManagerProxy import timber.log.Timber @@ -21,7 +21,7 @@ import javax.inject.Inject class CaptchaDialog : BaseDialogFragment() { @Inject - lateinit var sdk: Sdk + lateinit var wulkanowySdkFactory: WulkanowySdkFactory @Inject lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy @@ -59,7 +59,7 @@ class CaptchaDialog : BaseDialogFragment() { webView = this with(settings) { javaScriptEnabled = true - userAgentString = sdk.userAgent + userAgentString = wulkanowySdkFactory.create().userAgent } webViewClient = object : WebViewClient() { 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 77f03fb88..e69de29bb 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt @@ -1,43 +0,0 @@ -package io.github.wulkanowy.utils - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import timber.log.Timber - -fun Sdk.init(student: Student): Sdk { - email = student.email - password = student.password - symbol = student.symbol - schoolSymbol = student.schoolSymbol - studentId = student.studentId - classId = student.classId - isEduOne = student.isEduOne - emptyCookieJarInterceptor = true - - if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { - mobileBaseUrl = student.mobileBaseUrl - } else { - scrapperBaseUrl = student.scrapperBaseUrl - domainSuffix = student.scrapperDomainSuffix - loginType = Sdk.ScrapperLoginType.valueOf(student.loginType) - } - - mode = Sdk.Mode.valueOf(student.loginMode) - mobileBaseUrl = student.mobileBaseUrl - keyId = student.certificateKey - privatePem = student.privateKey - - Timber.d("Sdk in ${student.loginMode} mode reinitialized") - - return this -} - -fun Sdk.switchSemester(semester: Semester): Sdk { - return switchDiary( - diaryId = semester.diaryId, - kindergartenDiaryId = semester.kindergartenDiaryId, - schoolYear = semester.schoolYear, - unitId = semester.unitId, - ) -} diff --git a/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt new file mode 100644 index 000000000..dd1ce0569 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy + +import io.github.wulkanowy.data.WulkanowySdkFactory +import io.github.wulkanowy.sdk.Sdk +import io.mockk.every +import io.mockk.mockk + +fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk() + .apply { + every { create() } returns sdk + every { create(any(), any()) } answers { callOriginal() } + } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index e64144c2f..b34902363 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.TimetableDao @@ -16,8 +17,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -30,8 +31,8 @@ import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance class AttendanceRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var attendanceDb: AttendanceDao @@ -63,7 +64,8 @@ class AttendanceRepositoryTest { every { refreshHelper.shouldBeRefreshed(any()) } returns false coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList() - attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper) + attendanceRepository = + AttendanceRepository(attendanceDb, timetableDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt index f8f688501..e20603d22 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.errorOrNull @@ -15,8 +16,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -28,8 +29,8 @@ import io.github.wulkanowy.sdk.pojo.CompletedLesson as SdkCompletedLesson class CompletedLessonsRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var completedLessonDb: CompletedLessonsDao @@ -58,7 +59,7 @@ class CompletedLessonsRepositoryTest { every { refreshHelper.shouldBeRefreshed(any()) } returns false completedLessonRepository = - CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper) + CompletedLessonsRepository(completedLessonDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index d1ed9ca32..671c66f95 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.errorOrNull @@ -15,8 +16,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -28,8 +29,8 @@ import io.github.wulkanowy.sdk.pojo.Exam as SdkExam class ExamRemoteTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var examDb: ExamDao @@ -59,7 +60,7 @@ class ExamRemoteTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - examRepository = ExamRepository(examDb, sdk, refreshHelper) + examRepository = ExamRepository(examDb, wulkanowySdkFactory, refreshHelper) } @Test 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 0ea5d3fa4..0045badf1 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 @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao @@ -18,8 +19,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -35,8 +36,8 @@ import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade class GradeRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var gradeDb: GradeDao @@ -65,7 +66,7 @@ class GradeRepositoryTest { gradeDb = gradeDb, gradeSummaryDb = gradeSummaryDb, gradeDescriptiveDb = gradeDescriptiveDb, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, refreshHelper = refreshHelper, ) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index dfd36ee1a..6733190b7 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao @@ -13,9 +14,14 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -24,8 +30,8 @@ import org.junit.Test class GradeStatisticsRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var gradePartialStatisticsDb: GradePartialStatisticsDao @@ -54,7 +60,7 @@ class GradeStatisticsRepositoryTest { gradePartialStatisticsDb = gradePartialStatisticsDb, gradePointsStatisticsDb = gradePointsStatisticsDb, gradeSemesterStatisticsDb = gradeSemesterStatisticsDb, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, refreshHelper = refreshHelper, ) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index fa78b1bd3..854d5d548 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.errorOrNull @@ -12,8 +13,8 @@ import io.mockk.Runs import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -25,8 +26,8 @@ import io.github.wulkanowy.sdk.pojo.LuckyNumber as SdkLuckyNumber class LuckyNumberRemoteTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var luckyNumberDb: LuckyNumberDao @@ -43,7 +44,7 @@ class LuckyNumberRemoteTest { fun setUp() { MockKAnnotations.init(this) - luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, sdk) + luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, wulkanowySdkFactory) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index fbbe49345..9819fb1f7 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import android.content.Context +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MailboxDao @@ -28,10 +29,9 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking @@ -45,11 +45,10 @@ import java.time.Instant import java.time.ZoneOffset import kotlin.test.assertTrue -@OptIn(ExperimentalCoroutinesApi::class) class MessageRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var messageDb: MessagesDao @@ -102,7 +101,7 @@ class MessageRepositoryTest { messagesDb = messageDb, mutedMessageSendersDao = mutesDb, messageAttachmentDao = messageAttachmentDao, - sdk = sdk, + wulkanowySdkFactory = wulkanowySdkFactory, context = context, refreshHelper = refreshHelper, sharedPrefProvider = sharedPrefProvider, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index aa93a5e6f..5513a95fe 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.errorOrNull @@ -16,8 +17,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Assert @@ -28,8 +29,8 @@ import java.time.ZonedDateTime.of class MobileDeviceRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var mobileDeviceDb: MobileDeviceDao @@ -53,7 +54,8 @@ class MobileDeviceRepositoryTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - mobileDeviceRepository = MobileDeviceRepository(mobileDeviceDb, sdk, refreshHelper) + mobileDeviceRepository = + MobileDeviceRepository(mobileDeviceDb, wulkanowySdkFactory, refreshHelper) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt index e608cafb1..0ecaad9ea 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getMailboxEntity @@ -7,9 +8,14 @@ import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.MailboxType import io.github.wulkanowy.utils.AutoRefreshHelper -import io.mockk.* +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK +import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before @@ -18,8 +24,8 @@ import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient class RecipientLocalTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var recipientDb: RecipientDao @@ -63,7 +69,7 @@ class RecipientLocalTest { MockKAnnotations.init(this) every { refreshHelper.shouldBeRefreshed(any()) } returns false - recipientRepository = RecipientRepository(recipientDb, sdk, refreshHelper) + recipientRepository = RecipientRepository(recipientDb, wulkanowySdkFactory, refreshHelper) } @Test 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 96db8a794..3a18ac48a 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.TestDispatchersProvider +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getSemesterEntity @@ -12,8 +13,8 @@ import io.mockk.Runs import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just +import io.mockk.spyk import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -24,8 +25,8 @@ import java.time.LocalDate.now class SemesterRepositoryTest { - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var semesterDb: SemesterDao @@ -38,7 +39,8 @@ class SemesterRepositoryTest { fun initTest() { MockKAnnotations.init(this) - semesterRepository = SemesterRepository(semesterDb, sdk, TestDispatchersProvider()) + semesterRepository = + SemesterRepository(semesterDb, wulkanowySdkFactory, TestDispatchersProvider()) } @Test diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index 2a61f99ce..a14605435 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao @@ -18,9 +19,9 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK import io.mockk.just import io.mockk.mockk +import io.mockk.spyk import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -37,8 +38,8 @@ class TimetableRepositoryTest { @MockK(relaxed = true) private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper - @SpyK - private var sdk = Sdk() + private var sdk = spyk() + private val wulkanowySdkFactory = createWulkanowySdkFactoryMock(sdk) @MockK private lateinit var timetableDb: TimetableDao @@ -71,7 +72,7 @@ class TimetableRepositoryTest { timetableDb, timetableAdditionalDao, timetableHeaderDao, - sdk, + wulkanowySdkFactory, timetableNotificationSchedulerHelper, refreshHelper ) From b280316b07cf8093704abed1cb014c4c35393cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 21 Mar 2024 23:06:18 +0100 Subject: [PATCH 456/545] Bump sdk to 2.5.3-SNAPSHOT --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e73784a55..2d5d46663 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.2' + implementation 'io.github.wulkanowy:sdk:2.5.3-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From a0818de7d14801cd820b359f1df84e80b72f880a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 24 Mar 2024 14:55:05 +0100 Subject: [PATCH 457/545] Add isAdded condition to HomeworkAddDialog (#2499) --- .../wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt index c51370ea7..400d9f46b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -98,7 +98,9 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear, onDateSelected = { date = it - binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString()) + if (isAdded) { + binding.homeworkDialogDate.editText?.setText(date!!.toFormattedString()) + } } ) } From 3a55c3c760e308d6c7d493efa32e3d46dd2885ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 24 Mar 2024 18:55:23 +0100 Subject: [PATCH 458/545] Add ignoring FeatureUnavailableException in SyncWorker (#2500) --- .../main/java/io/github/wulkanowy/services/sync/SyncWorker.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 5dddd9a78..bcbc23ef2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -17,6 +17,7 @@ import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureUnavailableException import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.utils.DispatchersProvider @@ -48,6 +49,7 @@ class SyncWorker @AssistedInject constructor( val semester = semesterRepository.getCurrentSemester(student, true) student to semester } catch (e: Throwable) { + Timber.e(e) return@withContext getResultFromErrors(listOf(e)) } @@ -59,7 +61,7 @@ class SyncWorker @AssistedInject constructor( null } catch (e: Throwable) { Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") - if (e is FeatureDisabledException || e is FeatureNotAvailableException) { + if (e is FeatureDisabledException || e is FeatureNotAvailableException || e is FeatureUnavailableException) { null } else { Timber.e(e) From 76d038eefabfeb0a8522941695c9d786555daf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 24 Mar 2024 19:42:36 +0100 Subject: [PATCH 459/545] Hide grade statistics when is eduOne, add isEduOne flag migration (#2496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../63.json | 2547 +++++++++++++++++ .../wulkanowy/data/WulkanowySdkFactory.kt | 54 +- .../github/wulkanowy/data/db/AppDatabase.kt | 4 +- .../wulkanowy/data/db/dao/StudentDao.kt | 4 + .../wulkanowy/data/db/entities/Student.kt | 23 +- .../data/db/entities/StudentIsAuthorized.kt | 16 - .../data/db/migrations/Migration63.kt | 11 + .../data/repositories/StudentRepository.kt | 38 +- .../ui/modules/grade/GradeFragment.kt | 38 +- .../ui/modules/grade/GradePresenter.kt | 39 +- .../wulkanowy/ui/modules/grade/GradeView.kt | 2 + .../ui/modules/main/MainPresenter.kt | 8 +- .../modules/timetable/TimetablePresenter.kt | 2 +- .../timetablewidget/TimetableWidgetFactory.kt | 2 +- .../io/github/wulkanowy/utils/SdkExtension.kt | 0 .../wulkanowy/WulkanowySdkFactoryCreator.kt | 3 +- .../wulkanowy/data/WulkanowySdkFactoryTest.kt | 129 + .../db/migrations/AbstractMigrationTest.kt | 8 +- .../data/db/migrations/Migration54Test.kt | 6 +- .../data/db/migrations/Migration63Test.kt | 89 + 20 files changed, 2935 insertions(+), 88 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json delete mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/WulkanowySdkFactoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration63Test.kt diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json new file mode 100644 index 000000000..9c774bb4d --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/63.json @@ -0,0 +1,2547 @@ +{ + "formatVersion": 1, + "database": { + "version": 63, + "identityHash": "8c04a56e74b1c4f55302f28ede94cac0", + "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, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER DEFAULT 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": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "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, `author` TEXT, `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": "author", + "columnName": "author", + "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": "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, '8c04a56e74b1c4f55302f28ede94cac0')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt index 63890ea00..a4911f95a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -1,11 +1,15 @@ package io.github.wulkanowy.data import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsEduOne import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.RemoteConfigHelper import io.github.wulkanowy.utils.WebkitCookieManagerProxy +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -14,9 +18,12 @@ import javax.inject.Singleton class WulkanowySdkFactory @Inject constructor( private val chuckerInterceptor: ChuckerInterceptor, private val remoteConfig: RemoteConfigHelper, - private val webkitCookieManagerProxy: WebkitCookieManagerProxy + private val webkitCookieManagerProxy: WebkitCookieManagerProxy, + private val studentDb: StudentDao, ) { + private val eduOneMutex = Mutex() + private val sdk = Sdk().apply { androidVersion = android.os.Build.VERSION.RELEASE buildTag = android.os.Build.MODEL @@ -30,7 +37,12 @@ class WulkanowySdkFactory @Inject constructor( fun create() = sdk - fun create(student: Student, semester: Semester? = null): Sdk { + suspend fun create(student: Student, semester: Semester? = null): Sdk { + val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student) + return buildSdk(student, semester, overrideIsEduOne) + } + + private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk { return create().apply { email = student.email password = student.password @@ -39,7 +51,7 @@ class WulkanowySdkFactory @Inject constructor( studentId = student.studentId classId = student.classId emptyCookieJarInterceptor = true - isEduOne = student.isEduOne + isEduOne = isStudentEduOne if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { mobileBaseUrl = student.mobileBaseUrl @@ -62,4 +74,40 @@ class WulkanowySdkFactory @Inject constructor( } } } + + private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean { + if (student.isEduOne != null) return student.isEduOne + + eduOneMutex.withLock { + val studentFromDatabase = studentDb.loadById(student.id) + if (studentFromDatabase?.isEduOne != null) { + Timber.d("Migration eduOne: already done") + return studentFromDatabase.isEduOne + } + + Timber.d("Migration eduOne: flag missing. Running migration...") + val initializedSdk = buildSdk( + student = student, + semester = null, + isStudentEduOne = false, // doesn't matter + ) + val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Migration eduOne: can't get current student") } + .getOrNull() + + if (newCurrentStudent == null) { + Timber.d("Migration eduOne: failed, so skipping") + return false + } + + Timber.d("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}") + + val studentIsEduOne = StudentIsEduOne( + id = student.id, + isEduOne = newCurrentStudent.isEduOne + ) + studentDb.update(studentIsEduOne) + return newCurrentStudent.isEduOne + } + } } 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 3189fc312..ec22c5a3d 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 @@ -120,6 +120,7 @@ import io.github.wulkanowy.data.db.migrations.Migration55 import io.github.wulkanowy.data.db.migrations.Migration57 import io.github.wulkanowy.data.db.migrations.Migration58 import io.github.wulkanowy.data.db.migrations.Migration6 +import io.github.wulkanowy.data.db.migrations.Migration63 import io.github.wulkanowy.data.db.migrations.Migration7 import io.github.wulkanowy.data.db.migrations.Migration8 import io.github.wulkanowy.data.db.migrations.Migration9 @@ -175,6 +176,7 @@ import javax.inject.Singleton AutoMigration(from = 59, to = 60), AutoMigration(from = 60, to = 61), AutoMigration(from = 61, to = 62), + AutoMigration(from = 62, to = 63, spec = Migration63::class), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -183,7 +185,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 62 + const val VERSION_SCHEMA = 63 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 95abb399b..bfaecef64 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -10,6 +10,7 @@ import androidx.room.Update import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentIsAuthorized +import io.github.wulkanowy.data.db.entities.StudentIsEduOne import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import javax.inject.Singleton @@ -27,6 +28,9 @@ abstract class StudentDao { @Update(entity = Student::class) abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized) + @Update(entity = Student::class) + abstract suspend fun update(studentIsEduOne: StudentIsEduOne) + @Update(entity = Student::class) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) 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 23e4439aa..dbaa573ce 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 @@ -82,8 +82,8 @@ data class Student( @ColumnInfo(name = "is_authorized", defaultValue = "0") val isAuthorized: Boolean, - @ColumnInfo(name = "is_edu_one", defaultValue = "0") - val isEduOne: Boolean, + @ColumnInfo(name = "is_edu_one", defaultValue = "NULL") + val isEduOne: Boolean?, ) : Serializable { @@ -95,3 +95,22 @@ data class Student( @ColumnInfo(name = "avatar_color") var avatarColor = 0L } + +@Entity +data class StudentIsAuthorized( + + @PrimaryKey + var id: Long, + + @ColumnInfo(name = "is_authorized", defaultValue = "NULL") + val isAuthorized: Boolean?, +) : Serializable + +@Entity +data class StudentIsEduOne( + @PrimaryKey + var id: Long, + + @ColumnInfo(name = "is_edu_one", defaultValue = "NULL") + val isEduOne: Boolean?, +) : Serializable diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt deleted file mode 100644 index a1290177c..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentIsAuthorized.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.wulkanowy.data.db.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.io.Serializable - -@Entity -data class StudentIsAuthorized( - @ColumnInfo(name = "is_authorized", defaultValue = "0") - val isAuthorized: Boolean, -) : Serializable { - - @PrimaryKey - var id: Long = 0 -} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt new file mode 100644 index 000000000..f88d31fcc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration63.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.AutoMigrationSpec +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration63 : AutoMigrationSpec { + + override fun onPostMigrate(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0") + } +} 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 a5fea4ecb..245b3e6e7 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 @@ -16,9 +16,9 @@ import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.getCurrentOrLast import io.github.wulkanowy.utils.security.Scrambler import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -101,23 +101,29 @@ class StudentRepository @Inject constructor( return student } - suspend fun checkCurrentStudentAuthorizationStatus() { + suspend fun updateCurrentStudentAuthStatus() { + Timber.i("Check isAuthorized: started") val student = getCurrentStudent() - - if (!student.isAuthorized) { - val currentSemester = semesterDb.loadAll( - studentId = student.studentId, - classId = student.classId, - ).getCurrentOrLast() - val initializedSdk = wulkanowySdkFactory.create(student, currentSemester) - val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false - - if (isAuthorized) { - studentDb.update(StudentIsAuthorized(isAuthorized = true).apply { - id = student.id - }) - } else throw NoAuthorizationException() + if (student.isAuthorized) { + Timber.i("Check isAuthorized: already authorized") + return } + + val initializedSdk = wulkanowySdkFactory.create(student) + val newCurrentStudent = initializedSdk.getCurrentStudent() ?: return + + if (!newCurrentStudent.isAuthorized) { + Timber.i("Check isAuthorized: authorization required") + throw NoAuthorizationException() + } + + val studentIsAuthorized = StudentIsAuthorized( + id = student.id, + isAuthorized = true + ) + + Timber.i("Check isAuthorized: already authorized, update local status") + studentDb.update(studentIsAuthorized) } suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index 7ce07eb68..cc61dc25a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -7,7 +7,6 @@ import android.view.MenuItem import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import androidx.appcompat.app.AlertDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint @@ -31,14 +30,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade @Inject lateinit var presenter: GradePresenter - private val pagerAdapter by lazy { - BaseFragmentPagerAdapter( - fragmentManager = childFragmentManager, - pagesCount = 3, - lifecycle = lifecycle, - ) - } - private var semesterSwitchMenu: MenuItem? = null companion object { @@ -52,6 +43,8 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade override val currentPageIndex get() = binding.gradeViewPager.currentItem + private var pagerAdapter: BaseFragmentPagerAdapter? = null + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -71,13 +64,26 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade } override fun initView() { + with(binding) { + gradeErrorRetry.setOnClickListener { presenter.onRetry() } + gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun initTabs(pageCount: Int) { + pagerAdapter = BaseFragmentPagerAdapter( + lifecycle = lifecycle, + pagesCount = pageCount, + fragmentManager = childFragmentManager + ) + with(binding.gradeViewPager) { adapter = pagerAdapter offscreenPageLimit = 3 setOnSelectPageListener(presenter::onPageSelected) } - with(pagerAdapter) { + with(pagerAdapter!!) { containerId = binding.gradeViewPager.id titleFactory = { when (it) { @@ -99,11 +105,6 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade } binding.gradeTabLayout.elevation = requireContext().dpToPx(4f) - - with(binding) { - gradeErrorRetry.setOnClickListener { presenter.onRetry() } - gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() } - } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -169,19 +170,20 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade } override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView) + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView) ?.onParentLoadData(semesterId, forceRefresh) } override fun notifyChildParentReselected(index: Int) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() } override fun notifyChildSemesterChange(index: Int) { - (pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() + (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() } override fun onDestroyView() { + pagerAdapter = null presenter.onDetachView() super.onDestroyView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 0ae6521cf..8a70b3c19 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -22,11 +22,8 @@ class GradePresenter @Inject constructor( ) : BasePresenter(errorHandler, studentRepository) { private var selectedIndex = 0 - private var schoolYear = 0 - - private var semesters = emptyList() - + private var availableSemesters = emptyList() private val loadedSemesterId = mutableMapOf() private lateinit var lastError: Throwable @@ -40,7 +37,7 @@ class GradePresenter @Inject constructor( } fun onCreateMenu() { - if (semesters.isEmpty()) view?.showSemesterSwitch(false) + if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false) } fun onViewReselected() { @@ -49,8 +46,8 @@ class GradePresenter @Inject constructor( } fun onSemesterSwitch(): Boolean { - if (semesters.isNotEmpty()) { - view?.showSemesterDialog(selectedIndex - 1, semesters.take(2)) + if (availableSemesters.isNotEmpty()) { + view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2)) } return true } @@ -83,7 +80,7 @@ class GradePresenter @Inject constructor( } fun onPageSelected(index: Int) { - if (semesters.isNotEmpty()) loadChild(index) + if (availableSemesters.isNotEmpty()) loadChild(index) } fun onRetry() { @@ -101,16 +98,24 @@ class GradePresenter @Inject constructor( private fun loadData() { resourceFlow { val student = studentRepository.getCurrentStudent() - semesterRepository.getSemesters(student, refreshOnNoCurrent = true) + val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) + + student to semesters } .logResourceStatus("load grade data") - .onResourceData { - val current = it.getCurrentOrLast() - selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex - schoolYear = current.schoolYear - semesters = it.filter { semester -> semester.diaryId == current.diaryId } - view?.setCurrentSemesterName(current.semesterName, schoolYear) + .onResourceData { (student, semesters) -> + val currentSemester = semesters.getCurrentOrLast() + selectedIndex = + if (selectedIndex == 0) currentSemester.semesterName else selectedIndex + schoolYear = currentSemester.schoolYear + availableSemesters = semesters.filter { semester -> + semester.diaryId == currentSemester.diaryId + } + view?.run { + initTabs(if (student.isEduOne == true) 2 else 3) + setCurrentSemesterName(currentSemester.semesterName, schoolYear) + Timber.i("Loading grade data: Attempt load index $currentPageIndex") loadChild(currentPageIndex) showErrorView(false) @@ -131,10 +136,10 @@ class GradePresenter @Inject constructor( } private fun loadChild(index: Int, forceRefresh: Boolean = false) { - Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}") + Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}") val newSelectedSemesterId = try { - semesters.first { it.semesterName == selectedIndex }.semesterId + availableSemesters.first { it.semesterName == selectedIndex }.semesterId } catch (e: NoSuchElementException) { Timber.e(e, "Selected semester no exists") return diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt index 104f8505a..fc06c4809 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt @@ -9,6 +9,8 @@ interface GradeView : BaseView { fun initView() + fun initTabs(pageCount: Int) + fun showContent(show: Boolean) fun showProgress(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 67f729afb..a544381ce 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -73,7 +73,7 @@ class MainPresenter @Inject constructor( syncManager.startPeriodicSyncWorker() checkAppSupport() - checkCurrentStudentAuthorizationStatus() + updateCurrentStudentAuthStatus() analytics.logEvent("app_open", "destination" to initDestination.toString()) Timber.i("Main view was initialized with $initDestination") @@ -193,12 +193,10 @@ class MainPresenter @Inject constructor( view?.showStudentAvatar(currentStudent) } - private fun checkCurrentStudentAuthorizationStatus() { + private fun updateCurrentStudentAuthStatus() { presenterScope.launch { - runCatching { studentRepository.checkCurrentStudentAuthorizationStatus() } + runCatching { studentRepository.updateCurrentStudentAuthStatus() } .onFailure { errorHandler.dispatch(it) } - - Timber.i("Current student authorization status checked") } } } 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 abaf7d86b..8ef0772b8 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 @@ -150,7 +150,7 @@ class TimetablePresenter @Inject constructor( val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - isEduOne = student.isEduOne + isEduOne = student.isEduOne == true checkInitialAndCurrentDate(semester) timetableRepository.getTimetable( student = student, 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 5fb44a5e7..218c25834 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 @@ -78,7 +78,7 @@ class TimetableWidgetFactory( val lessons = getLessons(student, semester, date) val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) - createItems(lessons, lastSync, !student.isEduOne) + createItems(lessons, lastSync, !(student.isEduOne ?: false)) } .onFailure { items = listOf(TimetableWidgetItem.Error(it)) diff --git a/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SdkExtension.kt deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt index dd1ce0569..9623c9f98 100644 --- a/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/WulkanowySdkFactoryCreator.kt @@ -2,11 +2,12 @@ package io.github.wulkanowy import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.sdk.Sdk +import io.mockk.coEvery import io.mockk.every import io.mockk.mockk fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk() .apply { every { create() } returns sdk - every { create(any(), any()) } answers { callOriginal() } + coEvery { create(any(), any()) } returns sdk } diff --git a/app/src/test/java/io/github/wulkanowy/data/WulkanowySdkFactoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/WulkanowySdkFactoryTest.kt new file mode 100644 index 000000000..bedeffe74 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/WulkanowySdkFactoryTest.kt @@ -0,0 +1,129 @@ +package io.github.wulkanowy.data + +import android.os.Build +import dagger.hilt.android.testing.HiltTestApplication +import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentIsEduOne +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.RegisterStudent +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class) +class WulkanowySdkFactoryTest { + + private lateinit var wulkanowySdkFactory: WulkanowySdkFactory + private lateinit var studentDao: StudentDao + private lateinit var sdk: Sdk + + @Before + fun setUp() { + sdk = mockk(relaxed = true) + studentDao = mockk() + wulkanowySdkFactory = spyk( + WulkanowySdkFactory( + chuckerInterceptor = mockk(), + remoteConfig = mockk(relaxed = true), + webkitCookieManagerProxy = mockk(), + studentDb = studentDao + ) + ) + + every { wulkanowySdkFactory.create() } returns sdk + } + + @Test + fun `check sdk flag isEduOne when local student is eduone`() = runTest { + val student = getStudentEntity().copy(isEduOne = true) + + wulkanowySdkFactory.create(student) + + verify { sdk.isEduOne = true } + coVerify(exactly = 0) { sdk.getCurrentStudent() } + } + + @Test + fun `check sdk flag isEduOne when local student is not eduone`() = runTest { + val student = getStudentEntity().copy(isEduOne = false) + + wulkanowySdkFactory.create(student) + + verify { sdk.isEduOne = false } + coVerify(exactly = 0) { sdk.getCurrentStudent() } + } + + @Test + fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone true`() = + runTest { + val studentToProcess = getStudentEntity().copy(isEduOne = null) + val registerStudent = studentToProcess.toRegisterStudent(isEduOne = true) + + coEvery { studentDao.loadById(any()) } returns studentToProcess + coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs + coEvery { sdk.getCurrentStudent() } returns registerStudent + + wulkanowySdkFactory.create(studentToProcess) + + verify { sdk.isEduOne = true } + coVerify { sdk.getCurrentStudent() } + } + + @Test + fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone false`() = + runTest { + val studentToProcess = getStudentEntity().copy(isEduOne = null) + val registerStudent = studentToProcess.toRegisterStudent(isEduOne = false) + + coEvery { studentDao.loadById(any()) } returns studentToProcess + coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs + coEvery { sdk.getCurrentStudent() } returns registerStudent + + wulkanowySdkFactory.create(studentToProcess) + + verify { sdk.isEduOne = false } + coVerify { sdk.getCurrentStudent() } + } + + @Test + fun `check sdk flag isEduOne when sdk getCurrentStudent throws error`() = + runTest { + val studentToProcess = getStudentEntity().copy(isEduOne = null) + + coEvery { studentDao.loadById(any()) } returns studentToProcess + coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs + coEvery { sdk.getCurrentStudent() } throws Exception() + + wulkanowySdkFactory.create(studentToProcess) + + verify { sdk.isEduOne = false } + coVerify { sdk.getCurrentStudent() } + } + + private fun Student.toRegisterStudent(isEduOne: Boolean) = RegisterStudent( + studentId = studentId, + studentName = studentName, + studentSecondName = studentName, + studentSurname = studentName, + className = className, + classId = classId, + isParent = isParent, + isAuthorized = isAuthorized, + semesters = emptyList(), + isEduOne = isEduOne, + ) +} 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 18ff93392..0ac2a3cba 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 @@ -21,10 +21,10 @@ abstract class AbstractMigrationTest { @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - AppDatabase::class.java, - listOf(Migration55()), - FrameworkSQLiteOpenHelperFactory() + instrumentation = InstrumentationRegistry.getInstrumentation(), + databaseClass = AppDatabase::class.java, + specs = listOf(Migration63()), + openFactory = FrameworkSQLiteOpenHelperFactory() ) fun runMigrationsAndValidate(migration: Migration) { diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt index 1855e0d50..ccff05d88 100644 --- a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration54Test.kt @@ -7,8 +7,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltTestApplication import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.* -import kotlinx.coroutines.ExperimentalCoroutinesApi +import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLight +import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLightScoped +import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.STANDARD import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -19,7 +20,6 @@ import kotlin.test.assertEquals @HiltAndroidTest @RunWith(RobolectricTestRunner::class) -@OptIn(ExperimentalCoroutinesApi::class) @Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class) class Migration54Test : AbstractMigrationTest() { diff --git a/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration63Test.kt b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration63Test.kt new file mode 100644 index 000000000..dcca90696 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/db/migrations/Migration63Test.kt @@ -0,0 +1,89 @@ +package io.github.wulkanowy.data.db.migrations + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import android.os.Build +import androidx.sqlite.db.SupportSQLiteDatabase +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@HiltAndroidTest +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class) +class Migration63Test : AbstractMigrationTest() { + + @Test + fun `update is_edu_one to null if 0`() = runTest { + with(helper.createDatabase(dbName, 62)) { + createStudent(1, 0) + close() + } + + helper.runMigrationsAndValidate(dbName, 63, true) + + val database = getMigratedRoomDatabase() + val studentDb = database.studentDao + val student = studentDb.loadById(1) + + assertNull(student!!.isEduOne) + + database.close() + } + + @Test + fun `check is_edu_one is stay same`() = runTest { + with(helper.createDatabase(dbName, 62)) { + createStudent(1, 1) + close() + } + + helper.runMigrationsAndValidate(dbName, 63, true) + + val database = getMigratedRoomDatabase() + val studentDb = database.studentDao + val student = studentDb.loadById(1) + + val isEduOne = assertNotNull(student!!.isEduOne) + assertTrue(isEduOne) + database.close() + } + + private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) { + insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { + put("scrapper_base_url", "https://fakelog.cf") + put("mobile_base_url", "") + put("login_type", "SCRAPPER") + put("login_mode", "SCRAPPER") + put("certificate_key", "") + put("private_key", "") + put("is_parent", false) + put("email", "jan@fakelog.cf") + put("password", "******") + put("symbol", "symbol") + put("student_id", Random.nextInt()) + put("user_login_id", 123) + put("user_name", "studentName") + put("student_name", "studentName") + put("school_id", "123") + put("school_short", "") + put("school_name", "") + put("class_name", "") + put("class_id", Random.nextInt()) + put("is_current", false) + put("registration_date", "0") + put("id", id) + put("nick", "") + put("avatar_color", "") + put("is_edu_one", isEduOneValue) + }) + } +} From 38816782080db4b787ff02fdbfdecfea1071b852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 Mar 2024 23:22:07 +0100 Subject: [PATCH 460/545] Fix issues related to not authenticated eduOne students (#2501) --- .../data/repositories/StudentRepository.kt | 31 ++++++++++++++++--- .../ui/modules/auth/AuthPresenter.kt | 5 ++- 2 files changed, 30 insertions(+), 6 deletions(-) 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 245b3e6e7..61d6255b4 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 @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.exceptions.NoCurrentStudentException +import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToPojo import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.sdk.Sdk @@ -110,7 +111,22 @@ class StudentRepository @Inject constructor( } val initializedSdk = wulkanowySdkFactory.create(student) - val newCurrentStudent = initializedSdk.getCurrentStudent() ?: return + val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Check isAuthorized: error occurred") } + .getOrNull() + + if (newCurrentStudent == null) { + Timber.d("Check isAuthorized: current user is null") + return + } + + val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) + if (currentStudentSemesters.isEmpty()) { + Timber.d("Check isAuthorized: apply empty semesters workaround") + semesterDb.insertSemesters( + items = newCurrentStudent.semesters.mapToEntities(student.studentId), + ) + } if (!newCurrentStudent.isAuthorized) { Timber.i("Check isAuthorized: authorization required") @@ -178,15 +194,21 @@ class StudentRepository @Inject constructor( wulkanowySdkFactory.create(student, semester) .authorizePermission(pesel) - suspend fun refreshStudentName(student: Student, semester: Semester) { - val newCurrentApiStudent = wulkanowySdkFactory.create(student, semester) - .getCurrentStudent() ?: return + suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) { + val newCurrentApiStudent = wulkanowySdkFactory + .create(student, semester) + .getCurrentStudent() + ?: return Timber.d("Can't find student with id ${student.studentId}") val studentName = StudentName( studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}" ).apply { id = student.id } studentDb.update(studentName) + semesterDb.removeOldAndSaveNew( + oldItems = semesterDb.loadAll(student.studentId, semester.classId), + newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) + ) } suspend fun deleteStudentsAssociatedWithAccount(student: Student) { @@ -202,4 +224,3 @@ class StudentRepository @Inject constructor( } class NoAuthorizationException : Exception() - diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt index 3c061f498..5c597eeb4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject class AuthPresenter @Inject constructor( @@ -57,8 +58,9 @@ class AuthPresenter @Inject constructor( val semester = semesterRepository.getCurrentSemester(student) val isSuccess = studentRepository.authorizePermission(student, semester, pesel) + Timber.d("Auth succeed: $isSuccess") if (isSuccess) { - studentRepository.refreshStudentName(student, semester) + studentRepository.refreshStudentAfterAuthorize(student, semester) } isSuccess } @@ -68,6 +70,7 @@ class AuthPresenter @Inject constructor( view?.showContent(true) } .onSuccess { + Timber.d("Auth fully succeed: $it") if (it) { view?.showSuccess(true) view?.showContent(false) From b3faac01a50d60194f092e6b32ce9d24ab75c9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 24 Mar 2024 23:43:45 +0100 Subject: [PATCH 461/545] Version 2.5.3 --- 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 2d5d46663..d7174a2d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 151 - versionName "2.5.2" + versionCode 152 + versionName "2.5.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,7 +164,7 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.50d + userFraction = 0.20d updatePriority = 3 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.3-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 6eafb6a99..cd41a98be 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.5.2 +Wersja 2.5.3 -— naprawiliśmy omyłkowe wyświetlanie ekranu z wymaganą autoryzacją numerem PESEL -— naprawiliśmy kilka problemów ze stabilnością -— poprawiliśmy wyświetlanie kolorów we frekwencji +— naprawiliśmy wyświetlanie błędu "Brak uprawnień" po starcie aplikacji u użytkowników eduOne +— naprawiliśmy obsługę autoryzacji u użytkowników eduOne +— ukryliśmy numery lekcji i oceny klasy u użytkowników eduOne, bo VULCAN te funkcje usunął +— naprawiliśmy inne rzeczy u użytkowników eduOne (jak brak opisu oceny czy ładowanie danych na kilku ekranach) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 7fda4276d69260bab8bb13c9e04b56d1ce1fbdb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:31:46 +0000 Subject: [PATCH 462/545] Bump com.google.firebase:firebase-bom from 32.7.4 to 32.8.0 (#2504) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d3b365b60..3a71de337 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.7.4') + playImplementation platform('com.google.firebase:firebase-bom:32.8.0') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From 8c10606b6129f5fca49b395e2f279a56eb5a1af8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:32:07 +0000 Subject: [PATCH 463/545] Bump about_libraries from 11.1.0 to 11.1.1 (#2503) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f245e71b1..bb9f4179a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.23' - about_libraries = '11.1.0' + about_libraries = '11.1.1' hilt_version = '2.51' } repositories { From f13ce6e2b43985e8eaa8032326c9f540757a3150 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:32:28 +0000 Subject: [PATCH 464/545] Bump com.squareup.retrofit2:retrofit from 2.9.0 to 2.10.0 (#2502) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3a71de337..cf1b5c49f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'com.github.ncapdevi:FragNav:3.3.0' implementation "com.github.YarikSOffice:lingver:1.3.0" - implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:retrofit:2.10.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" From b7f7b16aef883a4572efd9366205b18cfbc50c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 26 Mar 2024 12:54:07 +0100 Subject: [PATCH 465/545] Add logging error from units and symbols during registration (#2507) --- app/build.gradle | 2 +- .../data/repositories/StudentRepository.kt | 23 +++++++++++++++---- .../wulkanowy/utils/SemesterExtension.kt | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d7174a2d3..d69ea5a34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.3' + implementation 'io.github.wulkanowy:sdk:2.5.4-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 61d6255b4..df47d7a63 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 @@ -42,6 +42,7 @@ class StudentRepository @Inject constructor( ): RegisterUser = wulkanowySdkFactory.create() .getStudentsFromHebe(token, pin, symbol, "") .mapToPojo(null) + .also { it.logErrors() } suspend fun getUserSubjectsFromScrapper( email: String, @@ -52,6 +53,7 @@ class StudentRepository @Inject constructor( ): RegisterUser = wulkanowySdkFactory.create() .getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol) .mapToPojo(password) + .also { it.logErrors() } suspend fun getStudentsHybrid( email: String, @@ -61,6 +63,7 @@ class StudentRepository @Inject constructor( ): RegisterUser = wulkanowySdkFactory.create() .getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol) .mapToPojo(password) + .also { it.logErrors() } suspend fun getSavedStudents(decryptPass: Boolean = true): List { return studentDb.loadStudentsWithSemesters().map { (student, semesters) -> @@ -195,10 +198,10 @@ class StudentRepository @Inject constructor( .authorizePermission(pesel) suspend fun refreshStudentAfterAuthorize(student: Student, semester: Semester) { - val newCurrentApiStudent = wulkanowySdkFactory - .create(student, semester) - .getCurrentStudent() - ?: return Timber.d("Can't find student with id ${student.studentId}") + val wulkanowySdk = wulkanowySdkFactory.create(student, semester) + val newCurrentApiStudent = runCatching { wulkanowySdk.getCurrentStudent() } + .onFailure { Timber.e(it, "Can't find student with id ${student.studentId}") } + .getOrNull() ?: return val studentName = StudentName( studentName = "${newCurrentApiStudent.studentName} ${newCurrentApiStudent.studentSurname}" @@ -221,6 +224,18 @@ class StudentRepository @Inject constructor( appDatabase.clearAllTables() } } + + private fun RegisterUser.logErrors() { + val symbolsErrors = symbols.filter { it.error != null } + .map { it.error } + val unitsErrors = symbols.flatMap { it.schools } + .filter { it.error != null } + .map { it.error } + + (symbolsErrors + unitsErrors).forEach { error -> + Timber.e(error, "Error occurred while fetching students") + } + } } class NoAuthorizationException : Exception() 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 e3b8a3b4c..6cfc4fa11 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/SemesterExtension.kt @@ -18,7 +18,7 @@ fun Semester.isCurrent(now: LocalDate = now()): Boolean { } fun List.getCurrentOrLast(): Semester { - if (isEmpty()) throw RuntimeException("Empty semester list") + if (isEmpty()) throw IllegalStateException("Empty semester list") // when there is only one current semester singleOrNull { it.isCurrent() }?.let { return it } From 8cdd4311a903f592c6bd110f0cd933b2de2f8f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Mar 2024 13:29:22 +0100 Subject: [PATCH 466/545] Version 2.5.4 --- app/build.gradle | 10 +++++----- app/src/main/play/release-notes/pl-PL/default.txt | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d69ea5a34..92633dd3e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 152 - versionName "2.5.3" + versionCode 153 + versionName "2.5.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -164,8 +164,8 @@ play { defaultToAppBundles = false track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS - userFraction = 0.20d - updatePriority = 3 + userFraction = 0.99d + updatePriority = 1 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.4-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 cd41a98be..d848d01c9 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,8 +1,5 @@ -Wersja 2.5.3 +Wersja 2.5.4 -— naprawiliśmy wyświetlanie błędu "Brak uprawnień" po starcie aplikacji u użytkowników eduOne -— naprawiliśmy obsługę autoryzacji u użytkowników eduOne -— ukryliśmy numery lekcji i oceny klasy u użytkowników eduOne, bo VULCAN te funkcje usunął -— naprawiliśmy inne rzeczy u użytkowników eduOne (jak brak opisu oceny czy ładowanie danych na kilku ekranach) +— naprawiliśmy kolejnych kilka błędów związanych z obsługą dziennika eduOne (tak, nie umiemy za jednym razem) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 254719f22f5564e0455570d27c82bbde390bcbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 26 Mar 2024 19:02:35 +0100 Subject: [PATCH 467/545] Remove classId from semester query when eduOne (#2509) --- app/build.gradle | 2 +- .../wulkanowy/data/db/dao/SemesterDao.kt | 12 +++++++ .../data/repositories/SemesterRepository.kt | 6 ++-- .../data/repositories/StudentRepository.kt | 4 +-- .../repositories/SemesterRepositoryTest.kt | 33 +++++++------------ 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 92633dd3e..1a74e198e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.4' + implementation 'io.github.wulkanowy:sdk:2.5.5-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt index 4d171907c..f50d95593 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt @@ -5,6 +5,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student import javax.inject.Singleton @Singleton @@ -16,4 +17,15 @@ interface SemesterDao : BaseDao { @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId") suspend fun loadAll(studentId: Int, classId: Int): List + + @Query("SELECT * FROM Semesters WHERE student_id = :studentId") + suspend fun loadAll(studentId: Int): List + + suspend fun loadAll(student: Student): List { + return if (student.isEduOne == true) { + loadAll(student.studentId) + } else { + loadAll(student.studentId, student.classId) + } + } } 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 da21f59ac..e15f93099 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 @@ -27,11 +27,11 @@ class SemesterRepository @Inject constructor( forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false ) = withContext(dispatchers.io) { - val semesters = semesterDb.loadAll(student.studentId, student.classId) + val semesters = semesterDb.loadAll(student) if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { refreshSemesters(student) - semesterDb.loadAll(student.studentId, student.classId) + semesterDb.loadAll(student) } else semesters } @@ -66,7 +66,7 @@ class SemesterRepository @Inject constructor( if (new.isEmpty()) return Timber.i("Empty semester list!") - val old = semesterDb.loadAll(student.studentId, student.classId) + val old = semesterDb.loadAll(student) semesterDb.removeOldAndSaveNew( oldItems = old uniqueSubtract new, newItems = new uniqueSubtract old, 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 df47d7a63..4fedcae96 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 @@ -123,7 +123,7 @@ class StudentRepository @Inject constructor( return } - val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) + val currentStudentSemesters = semesterDb.loadAll(student) if (currentStudentSemesters.isEmpty()) { Timber.d("Check isAuthorized: apply empty semesters workaround") semesterDb.insertSemesters( @@ -209,7 +209,7 @@ class StudentRepository @Inject constructor( studentDb.update(studentName) semesterDb.removeOldAndSaveNew( - oldItems = semesterDb.loadAll(student.studentId, semester.classId), + oldItems = semesterDb.loadAll(student), newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) ) } 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 3a18ac48a..57ace3e1d 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 @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.TestDispatchersProvider import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getSemesterPojo @@ -50,7 +51,7 @@ class SemesterRepositoryTest { getSemesterPojo(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { semesterDb.loadAll(student) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -76,12 +77,7 @@ class SemesterRepositoryTest { getSemesterPojo(123, 2, now().minusMonths(3), now()) ) - coEvery { - semesterDb.loadAll( - student.studentId, - student.classId - ) - } returns badSemesters.mapToEntities(student.studentId) + coEvery { semesterDb.loadAll(student) } returns badSemesters.mapToEntities(student.studentId) coEvery { sdk.getSemesters() } returns goodSemesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -103,7 +99,7 @@ class SemesterRepositoryTest { getSemesterPojo(2, 3, now(), now().plusMonths(6)), ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( + coEvery { semesterDb.loadAll(any()) } returnsMany listOf( badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId), goodSemesters.mapToEntities(student.studentId) @@ -125,7 +121,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + coEvery { semesterDb.loadAll(student) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -138,7 +134,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + coEvery { semesterDb.loadAll(student) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -151,7 +147,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now(), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + coEvery { semesterDb.loadAll(student) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -164,7 +160,7 @@ class SemesterRepositoryTest { getSemesterPojo(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { semesterDb.loadAll(student) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -193,12 +189,7 @@ class SemesterRepositoryTest { getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)), ) - coEvery { - semesterDb.loadAll( - student.studentId, - student.classId - ) - } returns semestersWithNoCurrent + coEvery { semesterDb.loadAll(student) } returns semestersWithNoCurrent coEvery { sdk.getSemesters() } returns newSemesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -214,7 +205,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1)) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + coEvery { semesterDb.loadAll(student) } returns semesters val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) assertEquals(2, items.size) @@ -227,14 +218,14 @@ class SemesterRepositoryTest { getSemesterEntity(1, 1, now(), now()) ) - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + coEvery { semesterDb.loadAll(student) } returns semesters runBlocking { semesterRepository.getCurrentSemester(student) } } @Test(expected = RuntimeException::class) fun getCurrentSemester_emptyList() { - coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { semesterDb.loadAll(student) } returns emptyList() coEvery { sdk.getSemesters() } returns emptyList() runBlocking { semesterRepository.getCurrentSemester(student) } From d799ec7ac9ac5f42b495faacfe592614e4b321db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 26 Mar 2024 19:19:44 +0100 Subject: [PATCH 468/545] Add skipping migration when previous attempt failed (#2508) --- .../wulkanowy/data/WulkanowySdkFactory.kt | 18 +++++++++++++++--- .../data/repositories/SemesterRepository.kt | 5 ++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt index a4911f95a..cede6f920 100644 --- a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -23,6 +23,7 @@ class WulkanowySdkFactory @Inject constructor( ) { private val eduOneMutex = Mutex() + private val migrationFailedStudentIds = mutableSetOf() private val sdk = Sdk().apply { androidVersion = android.os.Build.VERSION.RELEASE @@ -78,14 +79,24 @@ class WulkanowySdkFactory @Inject constructor( private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean { if (student.isEduOne != null) return student.isEduOne + if (student.id in migrationFailedStudentIds) { + Timber.i("Migration eduOne: skipping because of previous failure") + return false + } + eduOneMutex.withLock { + if (student.id in migrationFailedStudentIds) { + Timber.i("Migration eduOne: skipping because of previous failure") + return false + } + val studentFromDatabase = studentDb.loadById(student.id) if (studentFromDatabase?.isEduOne != null) { - Timber.d("Migration eduOne: already done") + Timber.i("Migration eduOne: already done") return studentFromDatabase.isEduOne } - Timber.d("Migration eduOne: flag missing. Running migration...") + Timber.i("Migration eduOne: flag missing. Running migration...") val initializedSdk = buildSdk( student = student, semester = null, @@ -96,7 +107,8 @@ class WulkanowySdkFactory @Inject constructor( .getOrNull() if (newCurrentStudent == null) { - Timber.d("Migration eduOne: failed, so skipping") + Timber.i("Migration eduOne: failed, so skipping") + migrationFailedStudentIds.add(student.id) return false } 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 e15f93099..ef7ae5950 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 @@ -64,7 +64,10 @@ class SemesterRepository @Inject constructor( .getSemesters() .mapToEntities(student.studentId) - if (new.isEmpty()) return Timber.i("Empty semester list!") + if (new.isEmpty()) { + Timber.i("Empty semester list from SDK!") + return + } val old = semesterDb.loadAll(student) semesterDb.removeOldAndSaveNew( From 7463cf6253cec6ab8bb88ee382627957db53c43b Mon Sep 17 00:00:00 2001 From: Faierbel Date: Tue, 26 Mar 2024 20:29:35 +0100 Subject: [PATCH 469/545] Replace function in DAO to 'OR' in SQL query --- .../wulkanowy/data/WulkanowySdkFactory.kt | 2 +- .../wulkanowy/data/db/dao/SemesterDao.kt | 14 +------- .../wulkanowy/data/db/dao/StudentDao.kt | 4 +-- .../data/repositories/SemesterRepository.kt | 6 ++-- .../data/repositories/StudentRepository.kt | 4 +-- .../repositories/SemesterRepositoryTest.kt | 33 ++++++++++++------- 6 files changed, 30 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt index cede6f920..83268a0e5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/data/WulkanowySdkFactory.kt @@ -112,7 +112,7 @@ class WulkanowySdkFactory @Inject constructor( return false } - Timber.d("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}") + Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}") val studentIsEduOne = StudentIsEduOne( id = student.id, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt index f50d95593..bf9a34d05 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt @@ -5,7 +5,6 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student import javax.inject.Singleton @Singleton @@ -15,17 +14,6 @@ interface SemesterDao : BaseDao { @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertSemesters(items: List): List - @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId") + @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)") suspend fun loadAll(studentId: Int, classId: Int): List - - @Query("SELECT * FROM Semesters WHERE student_id = :studentId") - suspend fun loadAll(studentId: Int): List - - suspend fun loadAll(student: Student): List { - return if (student.isEduOne == true) { - loadAll(student.studentId) - } else { - loadAll(student.studentId, student.classId) - } - } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index bfaecef64..5302b320c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -47,11 +47,11 @@ abstract class StudentDao { abstract suspend fun loadAll(): List @Transaction - @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id") + @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)") abstract suspend fun loadStudentsWithSemesters(): Map> @Transaction - @Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id") + @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id") abstract suspend fun loadStudentWithSemestersById(id: Long): Map> @Query("UPDATE Students SET is_current = 1 WHERE id = :id") 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 ef7ae5950..92d44650f 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 @@ -27,11 +27,11 @@ class SemesterRepository @Inject constructor( forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false ) = withContext(dispatchers.io) { - val semesters = semesterDb.loadAll(student) + val semesters = semesterDb.loadAll(student.studentId, student.classId) if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { refreshSemesters(student) - semesterDb.loadAll(student) + semesterDb.loadAll(student.studentId, student.classId) } else semesters } @@ -69,7 +69,7 @@ class SemesterRepository @Inject constructor( return } - val old = semesterDb.loadAll(student) + val old = semesterDb.loadAll(student.studentId, student.classId) semesterDb.removeOldAndSaveNew( oldItems = old uniqueSubtract new, newItems = new uniqueSubtract old, 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 4fedcae96..df47d7a63 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 @@ -123,7 +123,7 @@ class StudentRepository @Inject constructor( return } - val currentStudentSemesters = semesterDb.loadAll(student) + val currentStudentSemesters = semesterDb.loadAll(student.studentId, student.classId) if (currentStudentSemesters.isEmpty()) { Timber.d("Check isAuthorized: apply empty semesters workaround") semesterDb.insertSemesters( @@ -209,7 +209,7 @@ class StudentRepository @Inject constructor( studentDb.update(studentName) semesterDb.removeOldAndSaveNew( - oldItems = semesterDb.loadAll(student), + oldItems = semesterDb.loadAll(student.studentId, semester.classId), newItems = newCurrentApiStudent.semesters.mapToEntities(newCurrentApiStudent.studentId) ) } 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 57ace3e1d..3a18ac48a 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 @@ -3,7 +3,6 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.TestDispatchersProvider import io.github.wulkanowy.createWulkanowySdkFactoryMock import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getSemesterPojo @@ -51,7 +50,7 @@ class SemesterRepositoryTest { getSemesterPojo(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student) } returns emptyList() + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -77,7 +76,12 @@ class SemesterRepositoryTest { getSemesterPojo(123, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student) } returns badSemesters.mapToEntities(student.studentId) + coEvery { + semesterDb.loadAll( + student.studentId, + student.classId + ) + } returns badSemesters.mapToEntities(student.studentId) coEvery { sdk.getSemesters() } returns goodSemesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -99,7 +103,7 @@ class SemesterRepositoryTest { getSemesterPojo(2, 3, now(), now().plusMonths(6)), ) - coEvery { semesterDb.loadAll(any()) } returnsMany listOf( + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( badSemesters.mapToEntities(student.studentId), badSemesters.mapToEntities(student.studentId), goodSemesters.mapToEntities(student.studentId) @@ -121,7 +125,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) ) - coEvery { semesterDb.loadAll(student) } returns semesters + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -134,7 +138,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student) } returns semesters + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -147,7 +151,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now(), now()) ) - coEvery { semesterDb.loadAll(student) } returns semesters + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -160,7 +164,7 @@ class SemesterRepositoryTest { getSemesterPojo(1, 2, now().minusMonths(3), now()) ) - coEvery { semesterDb.loadAll(student) } returns emptyList() + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { sdk.getSemesters() } returns semesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -189,7 +193,12 @@ class SemesterRepositoryTest { getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)), ) - coEvery { semesterDb.loadAll(student) } returns semestersWithNoCurrent + coEvery { + semesterDb.loadAll( + student.studentId, + student.classId + ) + } returns semestersWithNoCurrent coEvery { sdk.getSemesters() } returns newSemesters coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs @@ -205,7 +214,7 @@ class SemesterRepositoryTest { getSemesterEntity(1, 2, now().minusMonths(1), now().plusMonths(1)) ) - coEvery { semesterDb.loadAll(student) } returns semesters + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters val items = semesterRepository.getSemesters(student, refreshOnNoCurrent = true) assertEquals(2, items.size) @@ -218,14 +227,14 @@ class SemesterRepositoryTest { getSemesterEntity(1, 1, now(), now()) ) - coEvery { semesterDb.loadAll(student) } returns semesters + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters runBlocking { semesterRepository.getCurrentSemester(student) } } @Test(expected = RuntimeException::class) fun getCurrentSemester_emptyList() { - coEvery { semesterDb.loadAll(student) } returns emptyList() + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { sdk.getSemesters() } returns emptyList() runBlocking { semesterRepository.getCurrentSemester(student) } From 4dc5fc65ac60d08d4d93cc7bbfd53123c534e5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 26 Mar 2024 20:37:51 +0100 Subject: [PATCH 470/545] Version 2.5.5 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a74e198e..01f4c3b16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 153 - versionName "2.5.4" + versionCode 154 + versionName "2.5.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.5-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 d848d01c9..9d72cb076 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.5.4 +Wersja 2.5.5 -— naprawiliśmy kolejnych kilka błędów związanych z obsługą dziennika eduOne (tak, nie umiemy za jednym razem) +— naprawiliśmy migrację informacji o tym, czy szkoła ucznia używa eduOne +— naprawiliśmy w końcu (teraz naprawdę mamy taką nadzieję) ten komunikat o braku uprawnień Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 6b59973624e20b9bd6fd5f1a1789dd9268a94aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:41:28 +0000 Subject: [PATCH 471/545] Bump hilt_version from 2.51 to 2.51.1 (#2510) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bb9f4179a..878758d5b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.9.23' about_libraries = '11.1.1' - hilt_version = '2.51' + hilt_version = '2.51.1' } repositories { mavenCentral() From b9f3ab2e56cdc7fb9dfbd8910f55f79cf0ef5ee0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:08:01 +0000 Subject: [PATCH 472/545] Bump com.squareup.retrofit2:retrofit from 2.10.0 to 2.11.0 (#2513) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ed452748a..bce1f208d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -233,7 +233,7 @@ dependencies { implementation 'com.github.ncapdevi:FragNav:3.3.0' implementation "com.github.YarikSOffice:lingver:1.3.0" - implementation 'com.squareup.retrofit2:retrofit:2.10.0' + implementation 'com.squareup.retrofit2:retrofit:2.11.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" From c34a369286e96e579de5a4f6fd8a0af3702c3ae7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:08:20 +0000 Subject: [PATCH 473/545] Bump org.robolectric:robolectric from 4.11.1 to 4.12.1 (#2514) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index bce1f208d..c0232e0b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -274,7 +274,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation 'org.robolectric:robolectric:4.11.1' + testImplementation 'org.robolectric:robolectric:4.12.1' testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test:core:1.5.0" From b500d8e2044f79e3daf05d9a9d647090cca4f610 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:09:38 +0000 Subject: [PATCH 474/545] Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2511) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 878758d5b..b715bdda5 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "ru.cian:huawei-publish-gradle-plugin:1.4.2" - classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.4.1.3373" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.0.0.4638" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries" } From 7a3a97447fb8e5413449a21101ce39c5b50d5d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 8 Apr 2024 20:51:05 +0200 Subject: [PATCH 475/545] Add isAdded check in AdditionalLessonAddDialog (#2515) --- .../timetable/additional/add/AdditionalLessonAddDialog.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt index 134719979..9a84fb0a2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -155,7 +155,9 @@ class AdditionalLessonAddDialog : BaseDialogFragment .build() timePicker.addOnPositiveButtonClickListener { - onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute)) + if (isAdded) { + onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute)) + } } if (!parentFragmentManager.isStateSaved) { From 16c51f7b07ccc91a0716688512796edb39504b40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:55:50 +0000 Subject: [PATCH 476/545] Bump com.google.firebase:firebase-bom from 32.8.0 to 32.8.1 (#2519) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c0232e0b0..7ab2dec16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'org.apache.commons:commons-text:1.11.0' - playImplementation platform('com.google.firebase:firebase-bom:32.8.0') + playImplementation platform('com.google.firebase:firebase-bom:32.8.1') playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-crashlytics:' From 7edd3df0744597176ce930a5d46da02c0f3df927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:56:16 +0000 Subject: [PATCH 477/545] Bump about_libraries from 11.1.1 to 11.1.3 (#2518) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b715bdda5..38d38c703 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.9.23' - about_libraries = '11.1.1' + about_libraries = '11.1.3' hilt_version = '2.51.1' } repositories { From 8b9b1460ab436a6533f33ed1d33086111d32c804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 13 Apr 2024 22:03:23 +0200 Subject: [PATCH 478/545] Update AuthDialog text (#2506) --- .../io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt | 8 ++++++-- app/src/main/res/layout/dialog_auth.xml | 5 ++--- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt index 5c597eeb4..0be086b69 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/auth/AuthPresenter.kt @@ -27,8 +27,12 @@ class AuthPresenter @Inject constructor( private fun loadName() { presenterScope.launch { - runCatching { studentRepository.getCurrentStudent(false) } - .onSuccess { view?.showDescriptionWithName(it.studentName) } + runCatching { + studentRepository.getCurrentStudent(false) + .studentName + .replace(" ", "\u00A0") + } + .onSuccess { view?.showDescriptionWithName(it) } .onFailure { errorHandler.dispatch(it) } } } diff --git a/app/src/main/res/layout/dialog_auth.xml b/app/src/main/res/layout/dialog_auth.xml index e2e2aa304..a0b9d6ea7 100644 --- a/app/src/main/res/layout/dialog_auth.xml +++ b/app/src/main/res/layout/dialog_auth.xml @@ -32,12 +32,11 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="24dp" android:layout_marginTop="8dp" - android:textSize="16sp" + android:textSize="14sp" app:layout_constraintTop_toBottomOf="@id/auth_title" - app:lineHeight="24sp" + app:lineHeight="18sp" tools:text="@string/auth_description" /> - 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 + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Skip for now From 895f5cbb761b60f9cdea4bf982d2db37192e7ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:24:38 +0000 Subject: [PATCH 479/545] Bump com.android.tools.build:gradle from 8.2.2 to 8.3.2 (#2517) --- app/src/main/AndroidManifest.xml | 2 +- app/src/play/AndroidManifest.xml | 11 +++++++++++ build.gradle | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 app/src/play/AndroidManifest.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e617c931..79d75bc01 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,7 +51,7 @@ android:exported="true" android:screenOrientation="portrait" android:theme="@style/WulkanowyTheme.SplashScreen" - tools:ignore="LockedOrientationActivity"> + tools:ignore="DiscouragedApi,LockedOrientationActivity"> diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml new file mode 100644 index 000000000..38f6306fe --- /dev/null +++ b/app/src/play/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/build.gradle b/build.gradle index 38d38c703..100c0eb9e 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ 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.19" - classpath 'com.android.tools.build:gradle:8.2.2' + classpath 'com.android.tools.build:gradle:8.3.2' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.4.1' classpath 'com.huawei.agconnect:agcp:1.9.1.303' From a487378dafd7400feb4412198643ed50ea358a12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:24:04 +0000 Subject: [PATCH 480/545] Bump androidx.core:core-ktx from 1.12.0 to 1.13.0 (#2521) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7ab2dec16..3505985c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.13.0' implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.activity:activity-ktx:1.8.2" implementation "androidx.appcompat:appcompat:1.6.1" From ff9be43291c490a51c08399e84e5de6e1299af47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:25:16 +0000 Subject: [PATCH 481/545] Bump org.apache.commons:commons-text from 1.11.0 to 1.12.0 (#2523) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3505985c0..2e0ded858 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,7 +246,7 @@ dependencies { implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'com.fredporciuncula:flow-preferences:1.9.1' - implementation 'org.apache.commons:commons-text:1.11.0' + implementation 'org.apache.commons:commons-text:1.12.0' playImplementation platform('com.google.firebase:firebase-bom:32.8.1') playImplementation 'com.google.firebase:firebase-analytics' From 860095e862acf4719c8856ad93e698b150455e7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:31:28 +0000 Subject: [PATCH 482/545] Bump androidx.activity:activity-ktx from 1.8.2 to 1.9.0 (#2522) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2e0ded858..2229f600b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -200,7 +200,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.13.0' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.activity:activity-ktx:1.8.2" + implementation "androidx.activity:activity-ktx:1.9.0" implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.fragment:fragment-ktx:1.6.2" implementation "androidx.annotation:annotation:1.7.1" From bbbafdfe70697e0893cf40a48558f53bd82e7843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 20:37:31 +0200 Subject: [PATCH 483/545] Bump sdk to 2.5.6-SNAPSHOT --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 01f4c3b16..e6f4846ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.5' + implementation 'io.github.wulkanowy:sdk:2.5.6-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From 4a65a5b19236d4cdb53a9d804cf8e91af7c4749e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 23:51:34 +0200 Subject: [PATCH 484/545] Migrate away from userLoginId to studentId due to vulcan last changes --- .../java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt | 4 ++-- .../io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt | 4 ++-- .../java/io/github/wulkanowy/data/db/entities/MobileDevice.kt | 4 ++-- .../github/wulkanowy/data/db/entities/SchoolAnnouncement.kt | 4 ++-- .../main/java/io/github/wulkanowy/data/db/entities/Student.kt | 1 + .../wulkanowy/data/mappers/DirectorInformationMapper.kt | 4 ++-- .../io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt | 2 +- .../wulkanowy/data/repositories/MobileDeviceRepository.kt | 2 +- .../data/repositories/SchoolAnnouncementRepository.kt | 4 ++-- .../ui/modules/debug/notification/mock/schoolAnnouncement.kt | 2 +- app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt | 2 +- 11 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt index 96382cc10..5ddb4dd08 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt @@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow @Dao interface MobileDeviceDao : BaseDao { - @Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC") - fun loadAll(userLoginId: Int): Flow> + @Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt index c32e4aba3..64d49bce7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt @@ -10,6 +10,6 @@ import javax.inject.Singleton @Singleton interface SchoolAnnouncementDao : BaseDao { - @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC") - fun loadAll(userLoginId: Int): Flow> + @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt index 89b04ccc8..44e900647 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt @@ -9,8 +9,8 @@ import java.time.Instant @Entity(tableName = "MobileDevices") data class MobileDevice( - @ColumnInfo(name = "user_login_id") - val userLoginId: Int, + @ColumnInfo(name = "user_login_id") // todo: change column name + val studentId: Int, @ColumnInfo(name = "device_id") val deviceId: Int, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt index ac096b02b..814a3c8dd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt @@ -9,8 +9,8 @@ import java.time.LocalDate @Entity(tableName = "SchoolAnnouncements") data class SchoolAnnouncement( - @ColumnInfo(name = "user_login_id") - val userLoginId: Int, + @ColumnInfo(name = "user_login_id") // todo: change column name + val studentId: Int, val date: LocalDate, 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 dbaa573ce..0300506ac 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 @@ -49,6 +49,7 @@ data class Student( @ColumnInfo(name = "student_id") val studentId: Int, + @Deprecated("not available in VULCAN anymore") @ColumnInfo(name = "user_login_id") val userLoginId: Int, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt index 85b37afc1..1a84a6a5e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement @JvmName("mapDirectorInformationToEntities") fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.date, subject = it.subject, content = it.content, @@ -19,7 +19,7 @@ fun List.mapToEntities(student: Student) = map { @JvmName("mapLastAnnouncementsToEntities") fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.date, subject = it.subject, content = it.content, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt index 1f4178fae..3818f01aa 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken fun List.mapToEntities(student: Student) = map { MobileDevice( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.createDate.toInstant(), deviceId = it.id, name = it.name diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 19466554a..1303d0e7a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -38,7 +38,7 @@ class MobileDeviceRepository @Inject constructor( val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired }, - query = { mobileDb.loadAll(student.userLoginId) }, + query = { mobileDb.loadAll(student.studentId) }, fetch = { wulkanowySdkFactory.create(student, semester) .getRegisteredDevices() diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index 6a04ce75f..78d956993 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -37,7 +37,7 @@ class SchoolAnnouncementRepository @Inject constructor( it.isEmpty() || forceRefresh || isExpired }, query = { - schoolAnnouncementDb.loadAll(student.userLoginId) + schoolAnnouncementDb.loadAll(student.studentId) }, fetch = { val sdk = wulkanowySdkFactory.create(student) @@ -57,7 +57,7 @@ class SchoolAnnouncementRepository @Inject constructor( ) fun getSchoolAnnouncementFromDatabase(student: Student): Flow> { - return schoolAnnouncementDb.loadAll(student.userLoginId) + return schoolAnnouncementDb.loadAll(student.studentId) } suspend fun updateSchoolAnnouncement(schoolAnnouncement: List) = diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt index e2dc5cd84..9b21f08e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt @@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf( private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement( subject = subject, content = content, - userLoginId = 0, + studentId = 0, date = LocalDate.now() ) diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index 721297513..e43654b8b 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -23,7 +23,7 @@ fun getRefreshKey(name: String, semester: Semester): String { } fun getRefreshKey(name: String, student: Student): String { - return "${name}_${student.userLoginId}" + return "${name}_${student.studentId}" } fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String { From af989ba9f62774e6888059c3f79987ea66676917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 23:59:29 +0200 Subject: [PATCH 485/545] Don't display brackets in login student select items when schoolShortName is blank --- .../studentselect/LoginStudentSelectAdapter.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index e6d131829..ef8cf4ee9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -19,19 +19,23 @@ class LoginStudentSelectAdapter @Inject constructor() : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (LoginStudentSelectItemType.values()[viewType]) { + return when (LoginStudentSelectItemType.entries[viewType]) { LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder( ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false), ) + LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder( ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder( ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.STUDENT -> StudentViewHolder( ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.HELP -> HelpViewHolder( ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false) ) @@ -98,9 +102,11 @@ class LoginStudentSelectAdapter @Inject constructor() : with(binding) { loginStudentSelectHeaderSchoolName.text = buildString { append(item.unit.schoolName.trim()) - append(" (") - append(item.unit.schoolShortName) - append(")") + if (item.unit.schoolShortName.isNotBlank()) { + append(" (") + append(item.unit.schoolShortName) + append(")") + } } loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty() loginStudentSelectHeaderSchoolError.text = item.unit.error?.message @@ -170,9 +176,11 @@ class LoginStudentSelectAdapter @Inject constructor() : oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> { oldItem.symbol == newItem.symbol } + oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> { oldItem.student == newItem.student } + else -> oldItem == newItem } From 6eca8c42f5b996b0f2dd36f81c2c52c886af978d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 00:11:29 +0200 Subject: [PATCH 486/545] Show graduated students on top of student select items list --- .../login/studentselect/LoginStudentSelectPresenter.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 344414180..c81d353d4 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 @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter @@ -108,8 +109,8 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun createItems(): List = buildList { - val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } - val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } + val notEmptySymbols = registerUser.symbols.filter { it.shouldShowOnTop() } + val emptySymbols = registerUser.symbols.filter { !it.shouldShowOnTop() } if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) { add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol })) @@ -127,6 +128,10 @@ class LoginStudentSelectPresenter @Inject constructor( add(helpItem) } + private fun RegisterSymbol.shouldShowOnTop(): Boolean { + return schools.isNotEmpty() || error is StudentGraduateException + } + private fun createNotEmptySymbolItems( notEmptySymbols: List, students: List, From d943d03266283d4a0822a43583b6d96434e4e8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 00:39:04 +0200 Subject: [PATCH 487/545] Version 2.5.6 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e6f4846ab..e3e8a7e00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 154 - versionName "2.5.5" + versionCode 155 + versionName "2.5.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -165,7 +165,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.99d - updatePriority = 1 + updatePriority = 3 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.6-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 9d72cb076..a966a31f6 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,5 @@ -Wersja 2.5.5 +Wersja 2.5.6 -— naprawiliśmy migrację informacji o tym, czy szkoła ucznia używa eduOne -— naprawiliśmy w końcu (teraz naprawdę mamy taką nadzieję) ten komunikat o braku uprawnień +— naprawiliśmy logowanie (pusta lista z wyborem uczniów), które zepsuło się po zmianach po stronie VULCANa Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From c72a117e3460414039460da870e7bbe0f0039eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 20:37:31 +0200 Subject: [PATCH 488/545] Bump sdk to 2.5.6-SNAPSHOT --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2229f600b..5c00122a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,7 +191,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.5' + implementation 'io.github.wulkanowy:sdk:2.5.6-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From 21211252835a77b7fe855b1be686a4cbcf6d197c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 23:51:34 +0200 Subject: [PATCH 489/545] Migrate away from userLoginId to studentId due to vulcan last changes --- .../java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt | 4 ++-- .../io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt | 4 ++-- .../java/io/github/wulkanowy/data/db/entities/MobileDevice.kt | 4 ++-- .../github/wulkanowy/data/db/entities/SchoolAnnouncement.kt | 4 ++-- .../main/java/io/github/wulkanowy/data/db/entities/Student.kt | 1 + .../wulkanowy/data/mappers/DirectorInformationMapper.kt | 4 ++-- .../io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt | 2 +- .../wulkanowy/data/repositories/MobileDeviceRepository.kt | 2 +- .../data/repositories/SchoolAnnouncementRepository.kt | 4 ++-- .../ui/modules/debug/notification/mock/schoolAnnouncement.kt | 2 +- app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt | 2 +- 11 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt index 96382cc10..5ddb4dd08 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/MobileDeviceDao.kt @@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow @Dao interface MobileDeviceDao : BaseDao { - @Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC") - fun loadAll(userLoginId: Int): Flow> + @Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt index c32e4aba3..64d49bce7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SchoolAnnouncementDao.kt @@ -10,6 +10,6 @@ import javax.inject.Singleton @Singleton interface SchoolAnnouncementDao : BaseDao { - @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC") - fun loadAll(userLoginId: Int): Flow> + @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC") + fun loadAll(studentId: Int): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt index 89b04ccc8..44e900647 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/MobileDevice.kt @@ -9,8 +9,8 @@ import java.time.Instant @Entity(tableName = "MobileDevices") data class MobileDevice( - @ColumnInfo(name = "user_login_id") - val userLoginId: Int, + @ColumnInfo(name = "user_login_id") // todo: change column name + val studentId: Int, @ColumnInfo(name = "device_id") val deviceId: Int, diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt index ac096b02b..814a3c8dd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/SchoolAnnouncement.kt @@ -9,8 +9,8 @@ import java.time.LocalDate @Entity(tableName = "SchoolAnnouncements") data class SchoolAnnouncement( - @ColumnInfo(name = "user_login_id") - val userLoginId: Int, + @ColumnInfo(name = "user_login_id") // todo: change column name + val studentId: Int, val date: LocalDate, 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 dbaa573ce..0300506ac 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 @@ -49,6 +49,7 @@ data class Student( @ColumnInfo(name = "student_id") val studentId: Int, + @Deprecated("not available in VULCAN anymore") @ColumnInfo(name = "user_login_id") val userLoginId: Int, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt index 85b37afc1..1a84a6a5e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/DirectorInformationMapper.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement @JvmName("mapDirectorInformationToEntities") fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.date, subject = it.subject, content = it.content, @@ -19,7 +19,7 @@ fun List.mapToEntities(student: Student) = map { @JvmName("mapLastAnnouncementsToEntities") fun List.mapToEntities(student: Student) = map { SchoolAnnouncement( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.date, subject = it.subject, content = it.content, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt index 1f4178fae..3818f01aa 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken fun List.mapToEntities(student: Student) = map { MobileDevice( - userLoginId = student.userLoginId, + studentId = student.studentId, date = it.createDate.toInstant(), deviceId = it.id, name = it.name diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index 19466554a..1303d0e7a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -38,7 +38,7 @@ class MobileDeviceRepository @Inject constructor( val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired }, - query = { mobileDb.loadAll(student.userLoginId) }, + query = { mobileDb.loadAll(student.studentId) }, fetch = { wulkanowySdkFactory.create(student, semester) .getRegisteredDevices() diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index 6a04ce75f..78d956993 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -37,7 +37,7 @@ class SchoolAnnouncementRepository @Inject constructor( it.isEmpty() || forceRefresh || isExpired }, query = { - schoolAnnouncementDb.loadAll(student.userLoginId) + schoolAnnouncementDb.loadAll(student.studentId) }, fetch = { val sdk = wulkanowySdkFactory.create(student) @@ -57,7 +57,7 @@ class SchoolAnnouncementRepository @Inject constructor( ) fun getSchoolAnnouncementFromDatabase(student: Student): Flow> { - return schoolAnnouncementDb.loadAll(student.userLoginId) + return schoolAnnouncementDb.loadAll(student.studentId) } suspend fun updateSchoolAnnouncement(schoolAnnouncement: List) = diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt index e2dc5cd84..9b21f08e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/schoolAnnouncement.kt @@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf( private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement( subject = subject, content = content, - userLoginId = 0, + studentId = 0, date = LocalDate.now() ) diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index 721297513..e43654b8b 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -23,7 +23,7 @@ fun getRefreshKey(name: String, semester: Semester): String { } fun getRefreshKey(name: String, student: Student): String { - return "${name}_${student.userLoginId}" + return "${name}_${student.studentId}" } fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String { From 622c75bb424363af77685c2cb8fe5cabd4fd55e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 21 Apr 2024 23:59:29 +0200 Subject: [PATCH 490/545] Don't display brackets in login student select items when schoolShortName is blank --- .../studentselect/LoginStudentSelectAdapter.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt index e6d131829..ef8cf4ee9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectAdapter.kt @@ -19,19 +19,23 @@ class LoginStudentSelectAdapter @Inject constructor() : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (LoginStudentSelectItemType.values()[viewType]) { + return when (LoginStudentSelectItemType.entries[viewType]) { LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder( ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false), ) + LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder( ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder( ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.STUDENT -> StudentViewHolder( ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false) ) + LoginStudentSelectItemType.HELP -> HelpViewHolder( ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false) ) @@ -98,9 +102,11 @@ class LoginStudentSelectAdapter @Inject constructor() : with(binding) { loginStudentSelectHeaderSchoolName.text = buildString { append(item.unit.schoolName.trim()) - append(" (") - append(item.unit.schoolShortName) - append(")") + if (item.unit.schoolShortName.isNotBlank()) { + append(" (") + append(item.unit.schoolShortName) + append(")") + } } loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty() loginStudentSelectHeaderSchoolError.text = item.unit.error?.message @@ -170,9 +176,11 @@ class LoginStudentSelectAdapter @Inject constructor() : oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> { oldItem.symbol == newItem.symbol } + oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> { oldItem.student == newItem.student } + else -> oldItem == newItem } From 2fa868173bd7b634579d1c4e06e2591a7612985f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 00:11:29 +0200 Subject: [PATCH 491/545] Show graduated students on top of student select items list --- .../login/studentselect/LoginStudentSelectPresenter.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 344414180..c81d353d4 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 @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter @@ -108,8 +109,8 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun createItems(): List = buildList { - val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() } - val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() } + val notEmptySymbols = registerUser.symbols.filter { it.shouldShowOnTop() } + val emptySymbols = registerUser.symbols.filter { !it.shouldShowOnTop() } if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.userEnteredSymbol }) { add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.userEnteredSymbol })) @@ -127,6 +128,10 @@ class LoginStudentSelectPresenter @Inject constructor( add(helpItem) } + private fun RegisterSymbol.shouldShowOnTop(): Boolean { + return schools.isNotEmpty() || error is StudentGraduateException + } + private fun createNotEmptySymbolItems( notEmptySymbols: List, students: List, From 2816d7217ab140c1f3df744d6b611bd9abef6836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 00:39:04 +0200 Subject: [PATCH 492/545] Version 2.5.6 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5c00122a2..2cfe55d3f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 154 - versionName "2.5.5" + versionCode 155 + versionName "2.5.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -161,7 +161,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.99d - updatePriority = 1 + updatePriority = 3 enabled.set(false) } @@ -191,7 +191,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.6-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 9d72cb076..a966a31f6 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,5 @@ -Wersja 2.5.5 +Wersja 2.5.6 -— naprawiliśmy migrację informacji o tym, czy szkoła ucznia używa eduOne -— naprawiliśmy w końcu (teraz naprawdę mamy taką nadzieję) ten komunikat o braku uprawnień +— naprawiliśmy logowanie (pusta lista z wyborem uczniów), które zepsuło się po zmianach po stronie VULCANa Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 82210c37e37a13d68924f014d079edba0f8e8fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 22:03:42 +0200 Subject: [PATCH 493/545] Display separate annual average and semester average if available (#2524) * Add displaying year average on grade details screen * Add displaying year average on grade summary screen * Add displaying year average on grade summary header * Fix tests * Hide semester average if it is not available in grade summary item * Add full names of summary averages labels --- app/build.gradle | 2 +- .../64.json | 2559 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 3 +- .../data/db/entities/GradeSummary.kt | 8 +- .../wulkanowy/data/mappers/GradeMapper.kt | 4 +- .../debug/notification/mock/gradeSummary.kt | 4 +- .../ui/modules/grade/GradeAverageProvider.kt | 8 +- .../grade/details/GradeDetailsAdapter.kt | 14 + .../modules/grade/details/GradeDetailsItem.kt | 1 + .../grade/details/GradeDetailsPresenter.kt | 3 +- .../grade/summary/GradeSummaryAdapter.kt | 57 +- .../main/res/layout/header_grade_details.xml | 21 +- .../main/res/layout/item_grade_summary.xml | 70 +- .../scrollable_header_grade_summary.xml | 74 +- app/src/main/res/values/strings.xml | 6 +- .../modules/grade/GradeAverageProviderTest.kt | 4 +- .../wulkanowy/utils/GradeExtensionTest.kt | 26 +- 17 files changed, 2816 insertions(+), 48 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json diff --git a/app/build.gradle b/app/build.gradle index e3e8a7e00..6d51ad841 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.6' + implementation 'io.github.wulkanowy:sdk:2.5.7-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json new file mode 100644 index 000000000..178a5eab5 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/64.json @@ -0,0 +1,2559 @@ +{ + "formatVersion": 1, + "database": { + "version": 64, + "identityHash": "dd5446e82ad8d0a65c545a5dbbaeb81c", + "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, `is_authorized` INTEGER NOT NULL DEFAULT 0, `is_edu_one` INTEGER DEFAULT 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": "isAuthorized", + "columnName": "is_authorized", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isEduOne", + "columnName": "is_edu_one", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "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, `points_sum_all_year` TEXT, `average` REAL NOT NULL, `average_all_year` REAL, `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": "pointsSumAllYear", + "columnName": "points_sum_all_year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "averageAllYear", + "columnName": "average_all_year", + "affinity": "REAL", + "notNull": false + }, + { + "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": "studentId", + "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, `author` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "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": "author", + "columnName": "author", + "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": "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_ok_visible` INTEGER NOT NULL DEFAULT 0, `is_x_visible` INTEGER NOT NULL DEFAULT 0, 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": "isOkVisible", + "columnName": "is_ok_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "isXVisible", + "columnName": "is_x_visible", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MutedMessageSenders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`author` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesDescriptive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `description` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT 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": "description", + "columnName": "description", + "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": [] + } + ], + "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, 'dd5446e82ad8d0a65c545a5dbbaeb81c')" + ] + } +} \ 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 ec22c5a3d..f23c79de0 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 @@ -177,6 +177,7 @@ import javax.inject.Singleton AutoMigration(from = 60, to = 61), AutoMigration(from = 61, to = 62), AutoMigration(from = 62, to = 63, spec = Migration63::class), + AutoMigration(from = 63, to = 64), ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -185,7 +186,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 63 + const val VERSION_SCHEMA = 64 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt index a42832ced..f8a357a39 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -33,7 +33,13 @@ data class GradeSummary( @ColumnInfo(name = "points_sum") val pointsSum: String, - val average: Double + @ColumnInfo(name = "points_sum_all_year") + val pointsSumAllYear: String?, + + val average: Double, + + @ColumnInfo(name = "average_all_year") + val averageAllYear: Double? = null, ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt index 66e922171..57322a7ae 100644 --- a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt @@ -37,9 +37,11 @@ fun List.mapToEntities(semester: Semester) = map { predictedGrade = it.predicted, finalGrade = it.final, pointsSum = it.pointsSum, + pointsSumAllYear = it.pointsSumAllYear, proposedPoints = it.proposedPoints, finalPoints = it.finalPoints, - average = it.average + average = it.average, + averageAllYear = it.averageAllYear, ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt index c452204b9..e92d1afb3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/gradeSummary.kt @@ -26,5 +26,7 @@ private fun generateSummary(subject: String, predicted: String, final: String) = proposedPoints = "", finalPoints = "", pointsSum = "", - average = .0 + average = .0, + pointsSumAllYear = null, + averageAllYear = null, ) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 8da59eaf4..7f14c01f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -266,7 +266,9 @@ class GradeAverageProvider @Inject constructor( proposedPoints = "", finalPoints = "", pointsSum = "", - average = .0 + pointsSumAllYear = null, + average = .0, + averageAllYear = null, ) } @@ -294,13 +296,15 @@ class GradeAverageProvider @Inject constructor( proposedPoints = "", finalPoints = "", pointsSum = "", + pointsSumAllYear = null, average = when { calcAverage -> details .updateModifiers(student, params) .calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage) else -> .0 - } + }, + averageAllYear = null, ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index 15b5db031..bcbd2df2f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -96,9 +96,11 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter HeaderViewHolder( HeaderGradeDetailsBinding.inflate(inflater, parent, false) ) + ViewType.ITEM.id -> ItemViewHolder( ItemGradeDetailsBinding.inflate(inflater, parent, false) ) + else -> throw IllegalStateException() } } @@ -110,6 +112,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter bindItemViewHolder( holder = holder, grade = items[position].value as Grade @@ -133,6 +136,10 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter ) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index d9621f51e..ec5d34c5e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -226,8 +226,9 @@ class GradeDetailsPresenter @Inject constructor( GradeDetailsHeader( subject = gradeSubject.subject, average = gradeSubject.average, + averageAllYear = gradeSubject.summary.averageAllYear, pointsSum = gradeSubject.points, - grades = subItems + grades = subItems, ).apply { newGrades = gradeSubject.grades.filter { grade -> !grade.isRead }.size }, ViewType.HEADER diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt index 95cf97bed..1cc74ef09 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.grade.summary import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R @@ -65,37 +66,55 @@ class GradeSummaryAdapter @Inject constructor( val gradeSummaries = items .filter { it.gradeDescriptive == null } .map { it.gradeSummary } + val isSecondSemester = items.any { item -> + item.gradeSummary.let { it.averageAllYear != null && it.averageAllYear != .0 } + } val context = binding.root.context val finalItemsCount = gradeSummaries.count { isGradeValid(it.finalGrade) } - val calculatedItemsCount = gradeSummaries.count { value -> value.average != 0.0 } + val calculatedSemesterItemsCount = gradeSummaries.count { value -> value.average != 0.0 } + val calculatedAnnualItemsCount = + gradeSummaries.count { value -> value.averageAllYear != 0.0 } val allItemsCount = gradeSummaries.count { !it.subject.equals("zachowanie", true) } val finalAverage = gradeSummaries.calcFinalAverage( - preferencesRepository.gradePlusModifier, - preferencesRepository.gradeMinusModifier + plusModifier = preferencesRepository.gradePlusModifier, + minusModifier = preferencesRepository.gradeMinusModifier, ) - val calculatedAverage = gradeSummaries.filter { value -> value.average != 0.0 } + val calculatedSemesterAverage = gradeSummaries.filter { value -> value.average != 0.0 } .map { values -> values.average } .reversed() // fix average precision .average() .let { if (it.isNaN()) 0.0 else it } + val calculatedAnnualAverage = gradeSummaries.filter { value -> value.averageAllYear != 0.0 } + .mapNotNull { values -> values.averageAllYear } + .reversed() // fix average precision + .average() + .let { if (it.isNaN()) 0.0 else it } with(binding) { + gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedSemesterAverage) + gradeSummaryScrollableHeaderCalculatedAnnual.text = + formatAverage(calculatedAnnualAverage) gradeSummaryScrollableHeaderFinal.text = formatAverage(finalAverage) - gradeSummaryScrollableHeaderCalculated.text = formatAverage(calculatedAverage) - gradeSummaryScrollableHeaderFinalSubjectCount.text = - context.getString( - R.string.grade_summary_from_subjects, - finalItemsCount, - allItemsCount - ) - gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString( + gradeSummaryScrollableHeaderFinalSubjectCount.text = context.getString( R.string.grade_summary_from_subjects, - calculatedItemsCount, + finalItemsCount, allItemsCount ) + gradeSummaryScrollableHeaderCalculatedSubjectCount.text = context.getString( + R.string.grade_summary_from_subjects, + calculatedSemesterItemsCount, + allItemsCount + ) + gradeSummaryScrollableHeaderCalculatedSubjectCountAnnual.text = context.getString( + R.string.grade_summary_from_subjects, + calculatedAnnualItemsCount, + allItemsCount + ) + gradeSummaryScrollableHeaderCalculatedAnnualContainer.isVisible = isSecondSemester gradeSummaryCalculatedAverageHelp.setOnClickListener { onCalculatedHelpClickListener() } + gradeSummaryCalculatedAverageHelpAnnual.setOnClickListener { onCalculatedHelpClickListener() } gradeSummaryFinalAverageHelp.setOnClickListener { onFinalHelpClickListener() } } } @@ -107,7 +126,12 @@ class GradeSummaryAdapter @Inject constructor( with(binding) { gradeSummaryItemTitle.text = gradeSummary.subject gradeSummaryItemPoints.text = gradeSummary.pointsSum + gradeSummaryItemAverage.text = formatAverage(gradeSummary.average, "") + gradeSummaryItemAverageAllYear.text = gradeSummary.averageAllYear?.let { + formatAverage(it, "") + } + gradeSummaryItemPredicted.text = "${gradeSummary.predictedGrade} ${gradeSummary.proposedPoints}".trim() gradeSummaryItemFinal.text = @@ -116,6 +140,12 @@ class GradeSummaryAdapter @Inject constructor( root.context.getString(R.string.all_no_data) } + gradeSummaryItemAverageContainer.isVisible = gradeSummary.average != .0 + gradeSummaryItemAverageDivider.isVisible = gradeSummaryItemAverageContainer.isVisible + gradeSummaryItemAverageAllYearContainer.isGone = + gradeSummary.averageAllYear == null || gradeSummary.averageAllYear == .0 + gradeSummaryItemAverageAllYearDivider.isGone = + gradeSummaryItemAverageAllYearContainer.isGone gradeSummaryItemFinalDivider.isVisible = gradeDescriptive == null gradeSummaryItemPredictedDivider.isVisible = gradeDescriptive == null gradeSummaryItemPointsDivider.isVisible = gradeDescriptive == null @@ -123,6 +153,7 @@ class GradeSummaryAdapter @Inject constructor( gradeSummaryItemFinalContainer.isVisible = gradeDescriptive == null gradeSummaryItemDescriptiveContainer.isVisible = gradeDescriptive != null gradeSummaryItemPointsContainer.isVisible = gradeSummary.pointsSum.isNotBlank() + gradeSummaryItemPointsDivider.isVisible = gradeSummaryItemPointsContainer.isVisible } } diff --git a/app/src/main/res/layout/header_grade_details.xml b/app/src/main/res/layout/header_grade_details.xml index e43e8993f..1765d9d1e 100644 --- a/app/src/main/res/layout/header_grade_details.xml +++ b/app/src/main/res/layout/header_grade_details.xml @@ -45,13 +45,30 @@ android:textColor="?android:textColorSecondary" android:textSize="12sp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum" + app:layout_constraintEnd_toStartOf="@id/gradeHeaderAverageAllYear" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject" app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject" tools:text="Average: 6,00" /> + + diff --git a/app/src/main/res/layout/item_grade_summary.xml b/app/src/main/res/layout/item_grade_summary.xml index 2c8c4ea37..f425bad83 100644 --- a/app/src/main/res/layout/item_grade_summary.xml +++ b/app/src/main/res/layout/item_grade_summary.xml @@ -20,20 +20,80 @@ android:id="@+id/gradeSummaryItemTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="40dp" android:layout_weight="1" android:textSize="17sp" tools:text="@tools:sample/lorem" /> + + + + + + + tools:text="2,50" /> + + + + + + + + + + + @@ -131,9 +191,9 @@ + android:textSize="16sp" + android:textStyle="bold" /> + + + + + + + + + + + + + + + android:textSize="16sp" + android:textStyle="bold" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2775365d5..3117eab9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,13 +126,17 @@ Comment Number of new ratings: %1$d Average: %1$.2f + Year: %1$.2f Points: %s No average + Semester average + Annual average Total points Final grade Predicted grade Descriptive grade - Calculated average + Calculated semester average + Calculated annual 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? 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 ad7bbe15f..b9f56efed 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 @@ -1679,7 +1679,9 @@ class GradeAverageProviderTest { finalPoints = "", finalGrade = "", predictedGrade = "", - position = 0 + position = 0, + pointsSumAllYear = null, + averageAllYear = null, ) } } diff --git a/app/src/test/java/io/github/wulkanowy/utils/GradeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/GradeExtensionTest.kt index 35dc4e5ba..37363f373 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/GradeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/GradeExtensionTest.kt @@ -23,13 +23,15 @@ class GradeExtensionTest { @Test fun calcWeightedAverage() { - assertEquals(3.47, listOf( - createGrade(5.0, 6.0, 0.33), - createGrade(5.0, 5.0, -0.33), - createGrade(4.0, 1.0, 0.0), - createGrade(1.0, 9.0, 0.5), - createGrade(0.0, .0, 0.0) - ).calcAverage(false), 0.005) + assertEquals( + 3.47, listOf( + createGrade(5.0, 6.0, 0.33), + createGrade(5.0, 5.0, -0.33), + createGrade(4.0, 1.0, 0.0), + createGrade(1.0, 9.0, 0.5), + createGrade(0.0, .0, 0.0) + ).calcAverage(false), 0.005 + ) } @Test @@ -86,7 +88,11 @@ class GradeExtensionTest { assertEquals(-.25, createGrade(5.0, .0, -.33).changeModifier(.0, .25).modifier, .0) } - private fun createGrade(value: Double, weightValue: Double = .0, modifier: Double = 0.25): Grade { + private fun createGrade( + value: Double, + weightValue: Double = .0, + modifier: Double = 0.25 + ): Grade { return Grade( semesterId = 1, studentId = 1, @@ -116,7 +122,9 @@ class GradeExtensionTest { proposedPoints = "", finalPoints = "", pointsSum = "", - average = .0 + average = .0, + pointsSumAllYear = null, + averageAllYear = null, ) } } From 43d13db07ce22e9e433f3ff3e9b7373775068b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 22:17:03 +0200 Subject: [PATCH 494/545] Update translations --- app/src/main/res/values-pl/strings.xml | 6 +++++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 33b715d75..895f7687a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -113,13 +113,17 @@ Komentarz Ilość nowych ocen: %1$d Średnia: %1$.2f + Roczna: %1$.2f Punkty: %s Brak średniej + Średnia semestralna + Średnia roczna Suma punktów Ocena końcowa Przewidywana ocena Ocena opisowa - Obliczona średnia + Obliczona średnia semestralna + Obliczona średnia roczna Jak działa obliczona średnia? Obliczona średnia jest średnią arytmetyczną obliczoną ze średnich przedmiotów. Pozwala ona na poznanie przybliżonej średniej końcowej. Jest obliczana w sposób wybrany przez użytkownika w ustawieniach aplikacji. Zaleca się wybranie odpowiedniej opcji. Dzieje się tak dlatego, że obliczanie średnich w szkołach różni się. Dodatkowo, jeśli twoja szkoła ma włączone średnie przedmiotów na stronie dziennika Vulcan, aplikacja pobiera je i ich nie oblicza. Można to zmienić, wymuszając obliczanie średniej w ustawieniach aplikacji.\n\nŚrednia ocen tylko z wybranego semestru:\n1. Obliczanie średniej arytmetycznej każdego przedmiotu w danym semestrze\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia ze średnich z obu semestrów:\n1.Obliczanie średniej arytmetycznej każdego przedmiotu w semestrze 1 i 2\n2. Obliczanie średniej arytmetycznej obliczonych średnich w semestrze 1 i 2 każdego przedmiotu.\n3. Zsumowanie obliczonych średnich\n4. Obliczanie średniej arytmetycznej zsumowanych średnich\n\nŚrednia wszystkich ocen z całego roku:\n1. Obliczanie średniej arytmetycznej z każdego przedmiotu w ciągu całego roku. Końcowa ocena w 1 semestrze jest bez znaczenia.\n2. Zsumowanie obliczonych średnich\n3. Obliczanie średniej arytmetycznej z zsumowanych średnich Jak działa końcowa średnia? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3117eab9d..957ad6de8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,7 +126,7 @@ Comment Number of new ratings: %1$d Average: %1$.2f - Year: %1$.2f + Annual: %1$.2f Points: %s No average Semester average From f6226e6b53a49ac6112e020ad6aaf52e65aa28a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 22 Apr 2024 22:36:05 +0200 Subject: [PATCH 495/545] Version 2.5.7 --- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6d51ad841..ede6dd951 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 155 - versionName "2.5.6" + versionCode 156 + versionName "2.5.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.7-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.7' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 a966a31f6..d96ff15ac 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.5.6 +Wersja 2.5.7 — naprawiliśmy logowanie (pusta lista z wyborem uczniów), które zepsuło się po zmianach po stronie VULCANa +— dodaliśmy wyświetlanie osobno średniej rocznej, bo VULCAN rozdzielił i zrobił się bałagan Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 6bf6a9da11567555f32f0f5e717f17c02f3ea0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 24 Apr 2024 19:27:11 +0200 Subject: [PATCH 496/545] New Crowdin updates (#2480) --- app/src/main/res/values-cs/strings.xml | 12 +++++++++--- app/src/main/res/values-de/strings.xml | 10 ++++++++-- app/src/main/res/values-pl/strings.xml | 4 +++- app/src/main/res/values-ru/strings.xml | 10 ++++++++-- app/src/main/res/values-sk/strings.xml | 12 +++++++++--- app/src/main/res/values-uk/strings.xml | 10 ++++++++-- app/src/main/res/values/strings.xml | 2 +- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 780d9351c..f37e1cf2a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -31,7 +31,7 @@ Semestr %1$d, %2$d/%3$d - Přihlaste se pomocí studentského nebo rodičovského účtu + Přihlaste se pomocí žákovského nebo rodičovského účtu Zadejte symbol ze stránky deníku: <b>%1$s</b> Uživatelské jméno Email @@ -113,13 +113,17 @@ Komentář Počet nových známek: %1$d Průměr: %1$.2f + Annual: %1$.2f Body: %s Bez průměru + Semester average + Annual average Součet bodů Konečná známka Předpokládaná známka Popisná známka - Vypočítaný průměr + Calculated semester average + Calculated annual average Jak funguje vypočítaný průměr? Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů Jak funguje konečný průměr? @@ -270,6 +274,7 @@ přesně v cíli %1$d pod cílem %1$d/%2$d přítomnosti + Nebyla zaznamenána žádná docházka Nepřítomnost ze školních důvodů Omluvená nepřítomnost Neomluvená nepřítomnost @@ -737,6 +742,7 @@ Vynutit průměrný výpočet podle aplikace Zobrazit přítomnost Cílová docházka + Zobrazit předměty bez docházek Třídění kalkulačky docházky Motiv Rozvíjení známek @@ -856,7 +862,7 @@ 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 + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Zatím přeskočit Webová stránka deníku VULCAN vyžaduje ověření diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7bc5aa990..b8bf5019f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -113,13 +113,17 @@ Kommentar Anzahl der neuen Bewertungen: %1$d Durchschnitt: %1$.2f + Annual: %1$.2f Punkte: %s Kein Durchschnitt + Semester average + Annual average Gesamtpunkte Finaler Note Vorhergesagte Note Descriptive grade - Berechnender Durchschnitt + Calculated semester average + Calculated annual average Wie funktioniert der berechnete Durchschnitt? Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte Wie funktioniert der endgültige Durchschnitt? @@ -242,6 +246,7 @@ right on target %1$d under target %1$d/%2$d presences + No attendances recorded Aus schulischen Gründen abwesend Entschuldigte Abwesenheit Unentschuldigtes Abwesenheit @@ -643,6 +648,7 @@ Mittelwertberechnung durch App erzwingen Anwesendheit zeigen Attendance target + Show subjects without any attendances Attendance calculator sorting Thema Steigende Sorten @@ -762,7 +768,7 @@ 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 + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Skip for now VULCAN\'s website requires verification diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 55c9db40d..48c90f599 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -274,6 +274,7 @@ dokładnie u celu %1$d poniżej celu %1$d/%2$d obecności + Nie odnotowano żadnej frekwencji Nieobecność z przyczyn szkolnych Nieobecność usprawiedliwiona Nieobecność nieusprawiedliwiona @@ -741,6 +742,7 @@ Wymuś obliczanie średniej przez aplikację Pokazuj obecność Docelowa obecność + Pokazuj przedmioty bez frekwencji Sortowanie kalkulatora obecności Motyw Rozwijanie ocen @@ -860,7 +862,7 @@ Potwierdź Autoryzacja zakończona pomyślnie Autoryzacja - Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Na razie pomiń Strona dziennika VULCAN wymaga weryfikacji diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0e7e0e1d2..d7f6e6cee 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -113,13 +113,17 @@ Комментарий Количество новых оценок: %1$d Средняя оценка: %1$.2f + Annual: %1$.2f Баллы: %s Нет средней оценки + Semester average + Annual average Сумма баллов Итоговая оценка Ожидаемая оценка Descriptive grade - Рассчитанная средняя оценка + Calculated semester average + Calculated annual average Как работает \"Рассчитанная средняя оценка\"? Рассчитанная средняя оценка - это среднее арифметическое, рассчитанное на основе средних оценок по предметам. Это позволяет узнать приблизительную итоговую среднюю оценку. Она рассчитывается способом, выбранным пользователем в настройках приложения. Рекомендуется выбрать подходящий вариант, так как каждая школа по разному считает среднюю оценку. Кроме того, если ваша школа выставляет средние оценки по предметам на странице Vulcan, приложение просто загрузит их. Это можно изменить, заставив приложение считать среднюю оценку в настройках.\n\nСредняя из оценок выбранного семестра:\n1. Вычисление средневзвешенного значения по каждому предмету за семестр\n2.Суммирование вычисленных значений\n3. Вычисление среднего арифметического суммированных значений\n\nСредняя из средних оценок семестров:\n1.Расчет средневзвешенного значения для каждого предмета в семестрах. \n2. Вычисление среднего арифметического из средневзвешенных значений для каждого предмета в семестрах.\n3. Суммирование средних арифметических\n4. Вычисление среднего арифматического из суммированных значений\n\nСредняя из оценок со всего года:\n1. Расчет средневзвешенного значения по каждому предмету за год. Итоговое среднее значение за 1 семестр не имеет значения.\n2. Суммирование вычисленных средних\n3. Расчет среднего арифметического суммированных чисел Как работает \"Итоговая средняя оценка\"? @@ -270,6 +274,7 @@ right on target %1$d under target %1$d/%2$d presences + No attendances recorded Отсутствие по школьным причинам Отсутствие по уважительной причине Отсутствие по неуважительной причине @@ -737,6 +742,7 @@ Принудительно высчитать среднюю оценку через приложение Показывать присутствия Attendance target + Show subjects without any attendances Attendance calculator sorting Тема Разворачивание оценок @@ -856,7 +862,7 @@ Авторизовать Авторизация прошла успешно Авторизация - Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Пропустить сейчас VULCAN\'s website requires verification diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9dbf72820..a84438944 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -31,7 +31,7 @@ Semester %1$d, %2$d/%3$d - Prihláste sa pomocou študentského alebo rodičovského konta + Prihláste sa pomocou žiackeho alebo rodičovského účtu Zadajte symbol zo stránky denníka: <b>%1$s</b> Užívateľské meno Email @@ -113,13 +113,17 @@ Komentár Počet nových známok %1$d Priemer: %1$.2f + Annual: %1$.2f Body: %s Bez priemeru + Semester average + Annual average Súčet bodov Konečná známka Predpokladaná známka Popisná známka - Vypočítaný priemer + Calculated semester average + Calculated annual average Ako funguje vypočítaný priemer? Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\nPriemer známok iba z vybraného semestra:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer priemerov z oboch semestrov:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer známok z celého roka:\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov Ako funguje konečný priemer? @@ -270,6 +274,7 @@ presne v cieli %1$d pod cieľom %1$d/%2$d prítomnosti + Nebola zaznamenaná žiadna dochádzka Neprítomnosť zo školských dôvodov Ospravedlnená neprítomnosť Neospravedlnená neprítomnosť @@ -737,6 +742,7 @@ Vynútiť priemerný výpočet podľa aplikácie Zobraziť prítomnosť Cieľová dochádzka + Zobraziť predmety bez dochádzok Triedenie kalkulačky dochádzky Motív Rozvijanie známok @@ -856,7 +862,7 @@ 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 + Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. Zatiaľ preskočiť Webová stránka denníka VULCAN vyžaduje overenie diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2d8ac1f4d..d936e8cdf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -113,13 +113,17 @@ Коментар Кількість нових оцінок: %1$d Середня оцінка: %1$.2f + Підсумкова: %1$.2f Бали: %s Середня оцінка відсутня + Середня за семестр + Підсумкова середня оцінка Всього балів Підсумкова оцінка Передбачувана оцінка Описова оцінка - Розрахована середня оцінка + Розрахована середня за семестр + Розрахована підсумкова середня оцінка Як працює \"Розрахована середня оцінка\"? Розрахована середня оцінка - це середнє арифметичне, обчислене з середніх оцінок з предметів. Це дозволяє дізнатися приблизну кінцеву середню оцінку. Вона розраховується спосібом, обраним користувачем у налаштуваннях програми. Рекомендується вибрати відповідний варіант, тому що кожна школа по різному розраховує середню оцінку. Крім того, якщо у вашій школі повідомляється середня оцінка з предметів на сторінці Vulcan, програма тільки завантажує ці оцінки і не розраховує їх самостійно. Це можна змінити шляхом примусового розрахунку середньоЇ оцінки в налаштуваннях програми.\n\nСередні оцінки тільки за обраний семестр:\n1. Розрахунок середньозваженого числа для кожного предмета в даному семестрі\n2. Сумування розрахованих числ\n3. Розрахунок середнього арифметичного з сумованих чисел\n\nСереднє значення з обох семестрів:\n1. Обчислення середньозваженого числа для кожного предмета у 1 та 2 семестрі\n2. Обчислення середнього арифметичного з розрахованих середньозважених числ за 1 та 2 семестри для кожного предмета.\n3. Додавання розрахованих середніх\n4. Розрахунок середнього арифметичного підсумованих середніх значень\n\nСереднє значення оцінок за весь рік: \n1. Розрахунок середньозваженого числа за рік для кожного предмета. Підсумковий середній показник у 1-му семестрі не має значення.\n2. Сумування розрахованих середніх\n3. Обчислення середнього арифметичного з суммованих середніх Як працює \"Підсумкова середня оцінка\"? @@ -270,6 +274,7 @@ точно у цілі %1$d під ціллю %1$d/%2$d відвідуваності + Немає жодних записаних відвідувань Відсутність зі шкільних причин Відсутність з поважних причин Відсутність без поважних причин @@ -737,6 +742,7 @@ Примусово розраховувати середню оцінку через додаток Показувати присутність Цільова відвідуваність + Показувати уроки без відвідувань Сортування калькулятора відвідування Тема Розгортання оцінок @@ -856,7 +862,7 @@ Авторизовать Авторизація пройшла успішно Авторизувати - Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче + Шановні батьки,<br><br>Для авторизації та забезпечення безпеки даних просимо Вас ввести нижче PESEL номер учня <b>%1$s</b>. Ці дані необхідні для правильного призначення доступу та захисту персональних даних відповідно до чинного законодавства.<br><br>Після введення даних буде проведена перевірка, щоб переконатися, що доступ до системи VULCAN надається виключно уповноваженим особам. У разі виникнення будь-яких сумнівів або проблем, будь ласка, зв\'яжіться з адміністратором шкільного щоденника для з\'ясування ситуації.<br><br>Ми підтримуємо найвищі стандарти захисту персональних даних і гарантуємо, що вся надана інформація є безпечною. Додаток Wulkanowy не зберігає і не обробляє номер PESEL.<br><br>Нагадуємо, що надання повних і точних даних є обов\'язковим і необхідним для використання системи VULCAN. Поки що пропустити Веб-сайт VULCAN потребує підтвердження diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 97bc6153b..9175321d3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -856,7 +856,7 @@ Authorize Authorization completed successfully Authorization - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
Skip for now From bc3aa7b8dc2686fb2a329e5a089bf496688f80f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 24 Apr 2024 22:28:16 +0200 Subject: [PATCH 497/545] New Crowdin updates (#2526) --- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index f37e1cf2a..9abd73c15 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -862,7 +862,7 @@ Autorizovat Autorizace byla úspěšně dokončena Autorizace - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
Zatím přeskočit Webová stránka deníku VULCAN vyžaduje ověření diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b8bf5019f..39fe5c14c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -768,7 +768,7 @@ Authorize Authorization completed successfully Authorization - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
Skip for now VULCAN\'s website requires verification diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 48c90f599..bdd5df91a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -862,7 +862,7 @@ Potwierdź Autoryzacja zakończona pomyślnie Autoryzacja - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Szanowny Rodzicu,

W celu autoryzacji i zapewnienia bezpieczeństwa danych, uprzejmie prosimy o wprowadzenie poniżej numeru PESEL ucznia %1$s. Te informacje są niezbędne do prawidłowego przypisania dostępu i ochrony danych osobowych zgodnie z obowiązującymi przepisami.

Po wprowadzeniu danych, będą one weryfikowane w celu zapewnienia, że dostęp do systemu VULCAN jest przyznawany wyłącznie upoważnionym osobom. W przypadku jakichkolwiek wątpliwości lub problemów, prosimy o kontakt z administratorem dziennika szkolnego w celu wyjaśnienia sytuacji.

Zachowujemy najwyższe standardy ochrony danych osobowych i zapewniamy, że wszelkie przekazane informacje są chronione. Wulkanowy nie przechowuje ani nie przetwarza numeru PESEL.

Przypominamy, że podanie pełnych i prawdziwych danych jest obowiązkowe i konieczne do korzystania z systemu VULCAN.
Na razie pomiń Strona dziennika VULCAN wymaga weryfikacji diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d7f6e6cee..6914641b6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -862,7 +862,7 @@ Авторизовать Авторизация прошла успешно Авторизация - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
Пропустить сейчас VULCAN\'s website requires verification diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index a84438944..8bb0e1bd8 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -862,7 +862,7 @@ Autorizovať Autorizácia bola úspešne dokončená Autorizácia - Dear Parent,<br><br>To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student <b>%1$s</b>. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.<br><br>After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.<br><br>We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.<br><br>We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system. + Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
Zatiaľ preskočiť Webová stránka denníka VULCAN vyžaduje overenie diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index d936e8cdf..70269211a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -862,7 +862,7 @@ Авторизовать Авторизація пройшла успішно Авторизувати - Шановні батьки,<br><br>Для авторизації та забезпечення безпеки даних просимо Вас ввести нижче PESEL номер учня <b>%1$s</b>. Ці дані необхідні для правильного призначення доступу та захисту персональних даних відповідно до чинного законодавства.<br><br>Після введення даних буде проведена перевірка, щоб переконатися, що доступ до системи VULCAN надається виключно уповноваженим особам. У разі виникнення будь-яких сумнівів або проблем, будь ласка, зв\'яжіться з адміністратором шкільного щоденника для з\'ясування ситуації.<br><br>Ми підтримуємо найвищі стандарти захисту персональних даних і гарантуємо, що вся надана інформація є безпечною. Додаток Wulkanowy не зберігає і не обробляє номер PESEL.<br><br>Нагадуємо, що надання повних і точних даних є обов\'язковим і необхідним для використання системи VULCAN. + Шановні батьки,

Для авторизації та забезпечення безпеки даних просимо Вас ввести нижче PESEL номер учня %1$s. Ці дані необхідні для правильного призначення доступу та захисту персональних даних відповідно до чинного законодавства.

Після введення даних буде проведена перевірка, щоб переконатися, що доступ до системи VULCAN надається виключно уповноваженим особам. У разі виникнення будь-яких сумнівів або проблем, будь ласка, зв\'яжіться з адміністратором шкільного щоденника для з\'ясування ситуації.

Ми підтримуємо найвищі стандарти захисту персональних даних і гарантуємо, що вся надана інформація є безпечною. Додаток Wulkanowy не зберігає і не обробляє номер PESEL.

Нагадуємо, що надання повних і точних даних є обов\'язковим і необхідним для використання системи VULCAN.
Поки що пропустити Веб-сайт VULCAN потребує підтвердження From dbc7587741ba6c6181fd84b585b1124b0fdcef36 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:44:59 +0200 Subject: [PATCH 498/545] Add settings button to attendance calculator (#2492) --- .../AttendanceCalculatorFragment.kt | 30 ++++++++++++++++++- .../AttendanceCalculatorPresenter.kt | 12 +++++++- .../calculator/AttendanceCalculatorView.kt | 2 ++ .../settings/appearance/AppearanceFragment.kt | 10 +++++++ .../action_menu_attendance_calculator.xml | 11 +++++++ app/src/main/res/values/preferences_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 ++ .../res/xml/scheme_preferences_appearance.xml | 5 ++++ 8 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/menu/action_menu_attendance_calculator.xml diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt index 2d5667015..63d1d8be5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorFragment.kt @@ -1,6 +1,9 @@ package io.github.wulkanowy.ui.modules.attendance.calculator import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager @@ -9,7 +12,9 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.AttendanceData import io.github.wulkanowy.databinding.FragmentAttendanceCalculatorBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.settings.appearance.AppearanceFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject @@ -33,6 +38,12 @@ class AttendanceCalculatorFragment : override val isViewEmpty get() = attendanceCalculatorAdapter.items.isEmpty() + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentAttendanceCalculatorBinding.bind(view) @@ -40,6 +51,19 @@ class AttendanceCalculatorFragment : presenter.onAttachView(this) } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.action_menu_attendance_calculator, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.attendance_calculator_menu_settings) presenter.onSettingsSelected() + else false + } + + override fun openSettingsView() { + (activity as? MainActivity)?.pushView(AppearanceFragment.withFocusedPreference(getString(R.string.pref_key_attendance_target))) + } + override fun initView() { with(binding.attendanceCalculatorRecycler) { layoutManager = LinearLayoutManager(context) @@ -50,7 +74,11 @@ class AttendanceCalculatorFragment : with(binding) { attendanceCalculatorSwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceCalculatorSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) - attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + attendanceCalculatorSwipe.setProgressBackgroundColorSchemeColor( + requireContext().getThemeAttrColor( + R.attr.colorSwipeRefresh + ) + ) attendanceCalculatorErrorRetry.setOnClickListener { presenter.onRetry() } attendanceCalculatorErrorDetails.setOnClickListener { presenter.onDetailsClick() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt index d292e5650..29cb2197f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorPresenter.kt @@ -1,6 +1,11 @@ package io.github.wulkanowy.ui.modules.attendance.calculator -import io.github.wulkanowy.data.* +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.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.domain.attendance.GetAttendanceCalculatorDataUseCase @@ -81,4 +86,9 @@ class AttendanceCalculatorPresenter @Inject constructor( } else showError(message, error) } } + + fun onSettingsSelected(): Boolean { + view?.openSettingsView() + return true + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt index 94e661212..21afe532e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/calculator/AttendanceCalculatorView.kt @@ -26,4 +26,6 @@ interface AttendanceCalculatorView : BaseView { fun updateData(data: List) fun clearView() + + fun openSettingsView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index ba234aae2..62544f83e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings.appearance import android.content.SharedPreferences import android.os.Bundle import android.view.View +import androidx.core.os.bundleOf import androidx.preference.PreferenceFragmentCompat import androidx.preference.SeekBarPreference import com.yariksoffice.lingver.Lingver @@ -30,9 +31,18 @@ class AppearanceFragment : PreferenceFragmentCompat(), override val titleStringId get() = R.string.pref_settings_appearance_title + companion object { + fun withFocusedPreference(key: String) = AppearanceFragment().apply { + arguments = bundleOf(FOCUSED_KEY to key) + } + + private const val FOCUSED_KEY = "focusedKey" + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) + arguments?.getString(FOCUSED_KEY)?.let { scrollToPreference(it) } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/res/menu/action_menu_attendance_calculator.xml b/app/src/main/res/menu/action_menu_attendance_calculator.xml new file mode 100644 index 000000000..c50906901 --- /dev/null +++ b/app/src/main/res/menu/action_menu_attendance_calculator.xml @@ -0,0 +1,11 @@ + +

+ + diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index 080456ef9..e87e9854a 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -1,6 +1,7 @@ default_menu_index + attendance_calculator attendance_present attendance_target attendance_calculator_sorting_mode diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9175321d3..a8e159ec6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -796,6 +796,8 @@ Dashboard Tiles visibility Attendance + Attendance calculator + Settings Timetable Grades Calculated average diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index a05d95c04..1ca4d2048 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -85,6 +85,11 @@ app:iconSpaceReserved="false" app:key="@string/pref_key_attendance_present" app:title="@string/pref_view_present" /> + + Date: Wed, 24 Apr 2024 22:46:55 +0200 Subject: [PATCH 499/545] Fix race condition of showing empty view in timetable (#2486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../history/LuckyNumberHistoryAdapter.kt | 2 +- .../ui/modules/timetable/TimetableAdapter.kt | 46 ++++++------- .../ui/modules/timetable/TimetableFragment.kt | 13 +++- .../modules/timetable/TimetablePresenter.kt | 23 ++++--- .../ui/modules/timetable/TimetableView.kt | 2 +- .../github/wulkanowy/utils/SyncListAdapter.kt | 66 +++++++++++++++++++ 6 files changed, 110 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt index 0c1b89c8e..9c718af45 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt @@ -33,4 +33,4 @@ class LuckyNumberHistoryAdapter @Inject constructor() : } class ItemViewHolder(val binding: ItemLuckyNumberHistoryBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} 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 a4221a2a2..b9b7a27e2 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 @@ -7,20 +7,20 @@ import android.view.ViewGroup import android.widget.TextView import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.ItemTimetableBinding import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding +import io.github.wulkanowy.utils.SyncListAdapter import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject class TimetableAdapter @Inject constructor() : - ListAdapter(differ) { + SyncListAdapter(Differ) { override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal @@ -61,12 +61,10 @@ 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, @@ -307,31 +305,29 @@ class TimetableAdapter @Inject constructor() : 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 = - when { - 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 + private object Differ : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean = + when { + oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> { + oldItem.lesson.start == newItem.lesson.start } - override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) = - oldItem == newItem + oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> { + oldItem.lesson.start == newItem.lesson.start + } - override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? { - return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) { - if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) { - "time_left" - } else super.getChangePayload(oldItem, newItem) - } else super.getChangePayload(oldItem, newItem) + else -> oldItem == newItem } + + override fun areContentsTheSame(oldItem: TimetableItem, newItem: TimetableItem) = + oldItem == newItem + + override fun getChangePayload(oldItem: TimetableItem, newItem: TimetableItem): Any? { + return if (oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal) { + if (oldItem.lesson == newItem.lesson && oldItem.showGroupsInPlan == newItem.showGroupsInPlan && oldItem.timeLeft != newItem.timeLeft) { + "time_left" + } else super.getChangePayload(oldItem, newItem) + } else super.getChangePayload(oldItem, newItem) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index 0e6459110..b73e7c26d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -21,7 +21,11 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -104,8 +108,11 @@ class TimetableFragment : BaseFragment(R.layout.fragme } } - override fun updateData(data: List) { - timetableAdapter.submitList(data) + override fun updateData(data: List, isDayChanged: Boolean) { + when { + isDayChanged -> timetableAdapter.recreate(data) + else -> timetableAdapter.submitList(data) + } } override fun clearData() { 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 8ef0772b8..111050618 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 @@ -81,7 +81,7 @@ class TimetablePresenter @Inject constructor( } else currentDate?.previousSchoolDay reloadView(date ?: return) - loadData() + loadData(isDayChanged = true) } fun onNextDay() { @@ -90,7 +90,7 @@ class TimetablePresenter @Inject constructor( } else currentDate?.nextSchoolDay reloadView(date ?: return) - loadData() + loadData(isDayChanged = true) } fun onPickDate() { @@ -104,7 +104,7 @@ class TimetablePresenter @Inject constructor( fun onSwipeRefresh() { Timber.i("Force refreshing the timetable") - loadData(true) + loadData(forceRefresh = true) } fun onRetry() { @@ -112,7 +112,7 @@ class TimetablePresenter @Inject constructor( showErrorView(false) showProgress(true) } - loadData(true) + loadData(forceRefresh = true) } fun onDetailsClick() { @@ -145,7 +145,7 @@ class TimetablePresenter @Inject constructor( return true } - private fun loadData(forceRefresh: Boolean = false) { + private fun loadData(forceRefresh: Boolean = false, isDayChanged: Boolean = false) { flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) @@ -169,9 +169,9 @@ class TimetablePresenter @Inject constructor( enableSwipe(true) showProgress(false) showErrorView(false) + updateData(it.lessons, isDayChanged) showContent(it.lessons.isNotEmpty()) showEmpty(it.lessons.isEmpty()) - updateData(it.lessons) setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content) reloadNavigation() } @@ -216,15 +216,14 @@ class TimetablePresenter @Inject constructor( } } - private fun updateData(lessons: List) { + private fun updateData(lessons: List, isDayChanged: Boolean) { tickTimer?.cancel() - if (currentDate != now()) { - view?.updateData(createItems(lessons)) - } else { - tickTimer = timer(period = 2_000) { + view?.updateData(createItems(lessons), isDayChanged) + if (currentDate == now()) { + tickTimer = timer(period = 2_000, initialDelay = 2_000) { Handler(Looper.getMainLooper()).post { - view?.updateData(createItems(lessons)) + view?.updateData(createItems(lessons), isDayChanged) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index 40190d51f..f4d5b7621 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -12,7 +12,7 @@ interface TimetableView : BaseView { fun initView() - fun updateData(data: List) + fun updateData(data: List, isDayChanged: Boolean) fun updateNavigationDay(date: String) diff --git a/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt new file mode 100644 index 000000000..e9135f498 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/SyncListAdapter.kt @@ -0,0 +1,66 @@ +package io.github.wulkanowy.utils + +import android.annotation.SuppressLint +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView + +/** + * Custom alternative to androidx.recyclerview.widget.ListAdapter. ListAdapter is asynchronous which + * caused data race problems in views when a Resource.Error arrived shortly after + * Resource.Intermediate/Success - occasionally in that case the user could see both the Resource's + * data and an error message one on top of the other. This is synchronized by design to avoid that + * problem, however it retains the quality of life improvements of the original. + */ +abstract class SyncListAdapter private constructor( + private val updateStrategy: SyncListAdapter.(List) -> Unit +) : RecyclerView.Adapter() { + + constructor(differ: DiffUtil.ItemCallback) : this({ newItems -> + val diffResult = DiffUtil.calculateDiff(toCallback(differ, items, newItems)) + items = newItems + diffResult.dispatchUpdatesTo(this) + }) + + var items = emptyList() + private set + + final override fun getItemCount() = items.size + + fun getItem(position: Int): T { + return items[position] + } + + /** + * Updates all items, same as submitList, however also disables animations temporarily. + * This prevents a flashing effect on some views. Should be used in favor of submitList when + * all data is changed (e.g. the selected day changes in timetable causing all lessons to change). + */ + @SuppressLint("NotifyDataSetChanged") + fun recreate(data: List) { + items = data + notifyDataSetChanged() + } + + fun submitList(data: List) { + updateStrategy(data.toList()) + } + + private fun toCallback( + itemCallback: DiffUtil.ItemCallback, + old: List, + new: List, + ) = object : DiffUtil.Callback() { + override fun getOldListSize() = old.size + + override fun getNewListSize() = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.areItemsTheSame(old[oldItemPosition], new[newItemPosition]) + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.areContentsTheSame(old[oldItemPosition], new[newItemPosition]) + + override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) = + itemCallback.getChangePayload(old[oldItemPosition], new[newItemPosition]) + } +} From 6a1851da13dd6678b1bf3e82cfde1b99d83efe43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 25 Apr 2024 09:26:47 +0200 Subject: [PATCH 500/545] Bump sdk to 2.5.8-SNAPSHOT --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ede6dd951..12fe81108 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.7' + implementation 'io.github.wulkanowy:sdk:2.5.8-SNAPSHOT' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' From f983a23b1a2f6033c089e10470c4edd018c05ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 25 Apr 2024 12:45:09 +0200 Subject: [PATCH 501/545] Version 2.5.8 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 12fe81108..7a2f0ca57 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 156 - versionName "2.5.7" + versionCode 157 + versionName "2.5.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -165,7 +165,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.99d - updatePriority = 3 + updatePriority = 4 enabled.set(false) } @@ -195,7 +195,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.5.8-SNAPSHOT' + implementation 'io.github.wulkanowy:sdk:2.5.8' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 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 d96ff15ac..2b87f21d5 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,5 @@ -Wersja 2.5.7 +Wersja 2.5.8 -— naprawiliśmy logowanie (pusta lista z wyborem uczniów), które zepsuło się po zmianach po stronie VULCANa -— dodaliśmy wyświetlanie osobno średniej rocznej, bo VULCAN rozdzielił i zrobił się bałagan +— obeszliśmy próby blokowania Wulkanowego przez firmę VULCAN, o czymś pewnie zapomnieliśmy, ale nie miejcie nam tego za złe Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From cde2121b60f5dadcd6fc884d12f568451aef1b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 26 Apr 2024 22:05:46 +0200 Subject: [PATCH 502/545] New Crowdin updates (#2527) --- app/src/main/res/values-cs/strings.xml | 14 +++-- .../main/res/values-de/preferences_values.xml | 10 +-- app/src/main/res/values-de/strings.xml | 62 ++++++++++--------- app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values-sk/strings.xml | 14 +++-- app/src/main/res/values-uk/strings.xml | 2 + 7 files changed, 59 insertions(+), 47 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9abd73c15..7dfab1872 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -113,17 +113,17 @@ Komentář Počet nových známek: %1$d Průměr: %1$.2f - Annual: %1$.2f + Roční: %1$.2f Body: %s Bez průměru - Semester average - Annual average + Pololetní průměr + Roční průměr Součet bodů Konečná známka Předpokládaná známka Popisná známka - Calculated semester average - Calculated annual average + Vypočítaný pololetní průměr + Vypočítaný roční průměr Jak funguje vypočítaný průměr? Vypočítaný průměr je aritmetický průměr vypočítaný z průměrů předmětů. Umožňuje vám to znát přibližný konečný průměr. Vypočítává se způsobem zvoleným uživatelem v nastavení aplikaci. Doporučuje se vybrat příslušnou možnost. Důvodem je rozdílný výpočet školních průměrů. Pokud vaše škola navíc uvádí průměr předmětů na stránce deníku Vulcan, aplikace si je stáhne a tyto průměry nepočítá. To lze změnit vynucením výpočtu průměru v nastavení aplikaci.\n\nPrůměr známek pouze z vybraného semestru:\n1. Výpočet váženého průměru pro každý předmět v daném semestru\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů\n\nPrůměr průměrů z obou semestrů:\n1. Výpočet váženého průměru pro každý předmět v semestru 1 a 2\n2. Výpočet aritmetického průměru vypočítaných průměrů za semestry 1 a 2 pro každý předmět.\n3. Sčítání vypočítaných průměrů\n4. Výpočet aritmetického průměru sečtených průměrů\n\nPrůměr známek z celého roku:\n1. Výpočet váženého průměru za rok pro každý předmět. Konečný průměr v 1. semestru je nepodstatný.\n2. Sčítání vypočítaných průměrů\n3. Výpočet aritmetického průměru součtených průměrů Jak funguje konečný průměr? @@ -811,6 +811,8 @@ Domů Viditelnost dlaždic Docházka + Kalkulačka docházky + Nastavení Plán lekce Známky Vypočítaný průměr @@ -862,7 +864,7 @@ Autorizovat Autorizace byla úspěšně dokončena Autorizace - Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
+ Vážený rodiči,

Chcete-li autorizovat a zajistit bezpečnost dat, prosíme Vás, abyste níže zadali PESEL číslo žáka %1$s. Tyto detaily jsou nutné pro správné přidělování přístupu k osobním údajům a jejich ochranu v souladu s platnými předpisy.

Po zadání údajů budou data ověřena, čímž se zajistí, že přístup do systému VULCAN získají pouze autorizované osoby. Pokud máte jakékoliv pochybnosti nebo problémy, kontaktujte prosím školního správce deníku pro objasnění situace.

Udržujeme nejvyšší standardy ochrany osobních údajů a zajišťujeme, aby byly všechny poskytnuté informace chráněné. Wulkanowy neukládá ani nezpracovává číslo PESEL.

Připomínáme, že poskytování úplných a přesných údajů je nutné a nezbytné k používání systému VULCAN.
Zatím přeskočit Webová stránka deníku VULCAN vyžaduje ověření diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 0170acfa3..7130cdd47 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -1,10 +1,10 @@ - Alphabetically - By date - By average - By attendance percentage - By subject attendance balance + Alphabetisch + Nach Datum + Nach Durchschnitt + Nach Anwesenheitsprozent + Nach Subjekt Anwesenheitssaldo Licht Dunkel diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 39fe5c14c..4945d13ce 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -10,10 +10,10 @@ Einstellungen Mehr Über die Applikation - Log viewer + Log Viewer Debuggen Benachrichtigungen debuggen - Clear webview cookies + Webview-Cookies löschen Mitarbeiter Lizenzen Nachrichten @@ -38,14 +38,14 @@ Anmeldung, PESEL oder e-mail Passwort UONET+ Registervariante - Custom domain suffix + Benutzerdefinierte Domeisensuffixe Mobile API Scraper Hybride Token PIN Symbol - E.g. \"lodz\" or \"powiatjaroslawski\" + Zum Beispiel \"lodz\" oder \"powiatjaroslawski\" Anmelden Passwort ist zu kurz Anmeldedaten sind falsch @@ -56,9 +56,9 @@ Ungültige email Den zugewiesenen Login anstelle von email verwenden Benutze den zugewiesenen Login oder E-Mail in @%1$s - Invalid domain suffix - Invalid symbol. If you cannot find it, please contact the school - Don\'t make this up! If you cannot find it, please contact the school + Ungültiges Domain-Suffix + Ungültiges Symbol. Wenn Sie es nicht finden können, wenden Sie sich bitte an die Schule + Denken Sie sich das nicht aus! Wenn Sie es nicht finden können, wenden Sie sich bitte an die Schule Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers Ausgewählter Student ist bereits angemeldet. 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 @@ -73,7 +73,7 @@ Discord email senden Stellen Sie sicher, dass Sie die richtige UONET+ Registervariation wählen! - Reset password + Passwort zurücksetzen Ihr Konto wiederherstellen Wiederherstellen Student ist bereits angemeldet @@ -81,13 +81,13 @@ Andere Suchorte Keine aktiven Schüler gefunden Geben Sie ein anderes Symbol ein - Get help - Full school name with the town (required) - Np. ZSTiO Jarosław lub SP nr 99 w Łodzi - Enter correct name of the school - Additional information in Polish (optional) - Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\" - Submit + Hilfe anfragen + Vollschulname mit der Stadt (erforderlich) + Z. B. ZSTiO Jarosław oder SP nr 99 w Łodzi + Geben Sie den richtigen Namen der Schule ein + Zusätzliche Informationen auf Polnisch (fakultativ) + Z. B. „Ich habe kürzlich die Schule gewechselt und...“ oder „Ich bin ein Elternteil und kann das Konto des anderen Kindes nicht sehen...“ + Einreichen Benachrichtigungen aktivieren Aktivieren Sie Benachrichtigungen, damit Sie keine Nachricht vom Lehrer oder eine neue Klasse verpassen @@ -98,8 +98,8 @@ Anmelden Die Sitzung ist abgelaufen Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein - Password has expired or been changed - Your account password has expired or been changed. You will need to log in to Wulkanowy again + Das Passwort ist abgelaufen oder wurde geändert + Ihr Passwort ist abgelaufen oder wurde geändert. Sie müssen sich erneut bei Wulkanowy anmelden Anwendungsunterstützung Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können Werbung aktivieren @@ -113,17 +113,17 @@ Kommentar Anzahl der neuen Bewertungen: %1$d Durchschnitt: %1$.2f - Annual: %1$.2f + Jährlich: %1$.2f Punkte: %s Kein Durchschnitt - Semester average - Annual average + Semesterdurchschnitt + Jahresdurchschnitt Gesamtpunkte Finaler Note Vorhergesagte Note - Descriptive grade - Calculated semester average - Calculated annual average + Deskriptive Note + Berechneter Semesterdurchschnitt + Berechneter Jahresdurchschnitt Wie funktioniert der berechnete Durchschnitt? Der berechnete Mittelwert ist das arithmetische Mittel, das aus den Durchschnittswerten der Probanden errechnet wird. Es erlaubt Ihnen, den ungefähre endgültigen Durchschnitt zu kennen. Sie wird auf eine vom Anwender in den Anwendungseinstellungen gewählte Weise berechnet. Es wird empfohlen, die entsprechende Option zu wählen. Das liegt daran, dass die Berechnung der Schuldurchschnitte unterschiedlich ist. Wenn Ihre Schule den Durchschnitt der Fächer auf der Vulcan-Seite angibt, lädt die Anwendung diese Fächer herunter und berechnet nicht den Durchschnitt. Dies kann geändert werden, indem die Berechnung des Durchschnitts in den Anwendungseinstellungen erzwungen wird. \n\nDurchschnitt der Noten nur aus dem ausgewählten Semester :\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in einem bestimmten Semester\n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Durchschnitte aus beiden Semestern:\n1. Berechnung des gewichteten Durchschnitts für jedes Fach in Semester 1 und 2\n2. Berechnung des arithmetischen Mittels der berechneten Durchschnitte für Semester 1 und 2 für jedes Fach. \n3. Hinzufügen von berechneten Durchschnittswerten\n4. Berechnung des arithmetischen Mittels der summierten Durchschnitte\nDurchschnitt der Noten aus dem ganzen Jahr:\n1. Berechnung des gewichteten Jahresdurchschnitts für jedes Fach. Der Abschlussdurchschnitt im 1. Semester ist irrelevant. \n2. Addition der berechneten Durchschnittswerte\n3. Berechnung des arithmetischen Mittels der summierten Mittelwerte Wie funktioniert der endgültige Durchschnitt? @@ -159,8 +159,8 @@ Neue Abschlussnoten - New descriptive grade - New descriptive grades + Neuer Deskriptive Grade + Neuer Deskriptive Grades Du hast %1$d Note bekommen @@ -175,8 +175,8 @@ Sie haben %1$d Abschlussnoten bekommen - You received %1$d descriptive grade - You received %1$d descriptive grades + Sie haben %1$d deskriptive Grade erhalten + Sie haben %1$d deskriptive Grades erhalten Lektion @@ -241,9 +241,9 @@ Endzeit muss grösser sein als Startzeit Übersicht über die Schulbesuch - Attendance calculator - %1$d over target - right on target + Anwesenheitsrechner + %1$d Über Ziel + direkt am ziel %1$d under target %1$d/%2$d presences No attendances recorded @@ -717,6 +717,8 @@ Dashboard Sichtbarkeit der Kacheln Schulbesuch + Attendance calculator + Settings Stundenplan Noten Berechneter Durchschnitt diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index bdd5df91a..212e442b9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -811,6 +811,8 @@ Start Widoczność kafelków Frekwencja + Kalkulator frekwencji + Ustawienia Plan lekcji Oceny Obliczona średnia diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6914641b6..87b458adc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -811,6 +811,8 @@ Главная Видимость плиток Посещаемость + Attendance calculator + Settings Расписание Оценки Рассчитанная средняя оценка diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 8bb0e1bd8..9f145efac 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -113,17 +113,17 @@ Komentár Počet nových známok %1$d Priemer: %1$.2f - Annual: %1$.2f + Ročný: %1$.2f Body: %s Bez priemeru - Semester average - Annual average + Polročný priemer + Ročný priemer Súčet bodov Konečná známka Predpokladaná známka Popisná známka - Calculated semester average - Calculated annual average + Vypočítaný polročný priemer + Vypočítaný ročný priemer Ako funguje vypočítaný priemer? Vypočítaný priemer je aritmetický priemer vypočítaný z priemerov predmetov. Umožňuje vám to poznať približný konečný priemer. Vypočítava sa spôsobom zvoleným užívateľom v nastaveniach aplikácii. Odporúča sa vybrať príslušnú možnosť. Dôvodom je rozdielny výpočet školských priemerov. Ak vaša škola navyše uvádza priemer predmetov na stránke denníka Vulcan, aplikácia si ich stiahne a tieto priemery nepočíta. To možno zmeniť vynútením výpočtu priemeru v nastavení aplikácii.\n\nPriemer známok iba z vybraného semestra:\n1. Výpočet váženého priemeru pre každý predmet v danom semestri\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer priemerov z oboch semestrov:\n1. Výpočet váženého priemeru pre každý predmet v semestri 1 a 2\n2. Výpočet aritmetického priemeru vypočítaných priemerov za semestre 1 a 2 pre každý predmet.\n3. Sčítanie vypočítaných priemerov\n4. Výpočet aritmetického priemeru součtených priemerov\n\nPriemer známok z celého roka:\n1. Výpočet váženého priemeru za rok pre každý predmet. Konečný priemer v 1. semestri je nepodstatný.\n2. Sčítanie vypočítaných priemerov\n3. Výpočet aritmetického priemeru součtených priemerov Ako funguje konečný priemer? @@ -811,6 +811,8 @@ Domov Viditeľnosť dlaždíc Dochádzka + Kalkulačka dochádzky + Nastavenia Plán lekcie Známky Vypočítaný priemer @@ -862,7 +864,7 @@ Autorizovať Autorizácia bola úspešne dokončená Autorizácia - Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
+ Vážený rodiči,

Ak chcete autorizovať a zaistiť bezpečnosť dát, prosíme Vás, aby ste nižšie zadali PESEL číslo žiaka %1$s. Tieto detaily sú nutné pre správne prideľovanie prístupu k osobným údajom a ich ochranu v súlade s platnými predpismi.

Po zadaní údajov budú dáta overené, čím sa zaistí, že prístup do systému VULCAN získajú iba autorizované osoby. Pokiaľ máte akékoľvek pochybnosti alebo problémy, kontaktujte prosím školského správcu denníka pre objasnenie situácie.

Udržujeme najvyššie štandardy ochrany osobných údajov a zaisťujeme, aby boli všetky poskytnuté informácie chránené. Wulkanowy neukladá ani nespracováva číslo PESEL.

Pripomíname, že poskytovanie úplných a presných údajov je nutné a nevyhnutné na používanie systému VULCAN.
Zatiaľ preskočiť Webová stránka denníka VULCAN vyžaduje overenie diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 70269211a..f48732c3c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -811,6 +811,8 @@ Головна Видимість плиток Відвідуваність + Attendance calculator + Settings Розклад Оцінки Розрахована середня оцінка From 6f2168d6417711be34d129a626cf31352a417571 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Fri, 26 Apr 2024 23:33:45 +0200 Subject: [PATCH 503/545] Additional lessons in timetable view (#2491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../data/enums/ShowAdditionalLessonsMode.kt | 11 ++ .../repositories/PreferencesRepository.kt | 7 + .../ui/modules/timetable/TimetableAdapter.kt | 24 +++ .../ui/modules/timetable/TimetableItem.kt | 8 +- .../modules/timetable/TimetablePresenter.kt | 130 ++++++++++----- .../additional/AdditionalLessonsFragment.kt | 14 +- .../additional/AdditionalLessonsPresenter.kt | 34 +++- .../additional/AdditionalLessonsView.kt | 2 +- .../add/AdditionalLessonAddDialog.kt | 14 +- .../add/AdditionalLessonAddPresenter.kt | 9 +- .../additional/add/AdditionalLessonAddView.kt | 2 +- .../layout/item_timetable_main_additional.xml | 153 ++++++++++++++++++ .../main/res/values/preferences_defaults.xml | 3 +- app/src/main/res/values/preferences_keys.xml | 1 + .../main/res/values/preferences_values.xml | 11 ++ app/src/main/res/values/strings.xml | 2 + .../res/xml/scheme_preferences_appearance.xml | 21 ++- 17 files changed, 379 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt create mode 100644 app/src/main/res/layout/item_timetable_main_additional.xml diff --git a/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt new file mode 100644 index 000000000..3e7cdef5b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/enums/ShowAdditionalLessonsMode.kt @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.enums + +enum class ShowAdditionalLessonsMode(val value: String) { + NONE("none"), + INLINE("inline"), + BELOW("below"); + + companion object { + fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE + } +} 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 2bb1538cb..8082068c5 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 @@ -14,6 +14,7 @@ import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeSortingMode +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode import io.github.wulkanowy.data.enums.TimetableGapsMode import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.ui.modules.dashboard.DashboardItem @@ -213,6 +214,12 @@ class PreferencesRepository @Inject constructor( ) ) + val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode + get() = getString( + R.string.pref_key_timetable_show_additional_lessons, + R.string.pref_default_timetable_show_additional_lessons + ).let { ShowAdditionalLessonsMode.getByValue(it) } + val gradeSortingMode: GradeSortingMode get() = GradeSortingMode.getByValue( getString( 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 b9b7a27e2..5cb6c401f 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,6 +12,7 @@ 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.ItemTimetableMainAdditionalBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding import io.github.wulkanowy.utils.SyncListAdapter import io.github.wulkanowy.utils.getPlural @@ -39,6 +40,10 @@ class TimetableAdapter @Inject constructor() : TimetableItemType.EMPTY -> EmptyViewHolder( ItemTimetableEmptyBinding.inflate(inflater, parent, false) ) + + TimetableItemType.ADDITIONAL -> AdditionalViewHolder( + ItemTimetableMainAdditionalBinding.inflate(inflater, parent, false) + ) } } @@ -69,6 +74,22 @@ class TimetableAdapter @Inject constructor() : binding = holder.binding, item = getItem(position) as TimetableItem.Empty, ) + + is AdditionalViewHolder -> bindAdditionalView( + binding = holder.binding, + item = getItem(position) as TimetableItem.Additional, + ) + } + } + + private fun bindAdditionalView( + binding: ItemTimetableMainAdditionalBinding, + item: TimetableItem.Additional + ) { + with(binding) { + timetableItemSubject.text = item.additional.subject + timetableItemTimeStart.text = item.additional.start.toFormattedString("HH:mm") + timetableItemTimeFinish.text = item.additional.end.toFormattedString("HH:mm") } } @@ -305,6 +326,9 @@ class TimetableAdapter @Inject constructor() : private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) : RecyclerView.ViewHolder(binding.root) + private class AdditionalViewHolder(val binding: ItemTimetableMainAdditionalBinding) : + RecyclerView.ViewHolder(binding.root) + private object Differ : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean = when { 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 402b03dd9..93290ba21 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.timetable import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional import java.time.Duration sealed class TimetableItem(val type: TimetableItemType) { @@ -23,6 +24,10 @@ sealed class TimetableItem(val type: TimetableItemType) { val numFrom: Int, val numTo: Int ) : TimetableItem(TimetableItemType.EMPTY) + + data class Additional( + val additional: TimetableAdditional, + ) : TimetableItem(TimetableItemType.ADDITIONAL) } data class TimeLeft( @@ -34,5 +39,6 @@ data class TimeLeft( enum class TimetableItemType { SMALL, NORMAL, - EMPTY + EMPTY, + ADDITIONAL, } 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 111050618..c00bdc3e4 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 @@ -4,6 +4,9 @@ import android.os.Handler import android.os.Looper import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.BELOW +import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode.NONE 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 @@ -14,6 +17,7 @@ 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.pojos.TimetableFull import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository @@ -169,9 +173,9 @@ class TimetablePresenter @Inject constructor( enableSwipe(true) showProgress(false) showErrorView(false) - updateData(it.lessons, isDayChanged) - showContent(it.lessons.isNotEmpty()) - showEmpty(it.lessons.isEmpty()) + updateData(it, isDayChanged) + showContent(it.lessons.isNotEmpty() || it.additional.isNotEmpty()) + showEmpty(it.lessons.isEmpty() && it.additional.isEmpty()) setDayHeaderMessage(it.headers.find { header -> header.date == currentDate }?.content) reloadNavigation() } @@ -216,7 +220,7 @@ class TimetablePresenter @Inject constructor( } } - private fun updateData(lessons: List, isDayChanged: Boolean) { + private fun updateData(lessons: TimetableFull, isDayChanged: Boolean) { tickTimer?.cancel() view?.updateData(createItems(lessons), isDayChanged) @@ -229,53 +233,84 @@ class TimetablePresenter @Inject constructor( } } - private fun createItems(items: List): List { - val filteredItems = items - .filter { - if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { - it.isStudentPlan - } else true - } - .sortedWith(compareBy({ item -> item.start }, { item -> !item.isStudentPlan })) + private sealed class Item( + val isStudentPlan: Boolean, + val start: Instant, + val number: Int?, + ) { + class Lesson(val lesson: Timetable) : + Item(lesson.isStudentPlan, lesson.start, lesson.number) + + class Additional(val additional: TimetableAdditional) : Item(true, additional.start, null) + } + + private fun createItems(fullTimetable: TimetableFull): List { + val showAdditionalLessonsInPlan = prefRepository.showAdditionalLessonsInPlan + val allItems = + fullTimetable.lessons.map(Item::Lesson) + fullTimetable.additional.map(Item::Additional) + .takeIf { showAdditionalLessonsInPlan != NONE }.orEmpty() + + val filteredItems = allItems.filter { + if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { + it.isStudentPlan + } else true + }.sortedWith( + (compareBy { it is Item.Additional } + .takeIf { showAdditionalLessonsInPlan == BELOW } ?: EmptyComparator()) + .thenBy { it.start } + .thenBy { !it.isStudentPlan } + ) var prevNum = when (prefRepository.showTimetableGaps) { BETWEEN_AND_BEFORE_LESSONS -> 0 else -> null } + var prevIsAdditional = false 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 (prefRepository.showTimetableGaps != NO_GAPS) { + if (prevNum != null && it.number != null && it.number > prevNum!! + 1) { + if (!prevIsAdditional) { + // Additional lessons do count as a lesson so don't add empty lessons + // when there is an additional lesson present + val emptyLesson = TimetableItem.Empty( + numFrom = prevNum!! + 1, numTo = it.number - 1 + ) + add(emptyLesson) + } + } + prevNum = it.number + prevIsAdditional = it is Item.Additional } - if (it.isStudentPlan) { - val normalLesson = TimetableItem.Normal( - lesson = it, - showGroupsInPlan = prefRepository.showGroupsInPlan, - timeLeft = filteredItems.getTimeLeftForLesson(it, i), - onClick = ::onTimetableItemSelected, - isLessonNumberVisible = !isEduOne - ) - add(normalLesson) - } else { - val smallLesson = TimetableItem.Small( - lesson = it, - onClick = ::onTimetableItemSelected, - isLessonNumberVisible = !isEduOne - ) - add(smallLesson) + if (it is Item.Lesson) { + if (it.isStudentPlan) { + val normalLesson = TimetableItem.Normal( + lesson = it.lesson, + showGroupsInPlan = prefRepository.showGroupsInPlan, + timeLeft = filteredItems.getTimeLeftForLesson(it.lesson, i), + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne + ) + add(normalLesson) + } else { + val smallLesson = TimetableItem.Small( + lesson = it.lesson, + onClick = ::onTimetableItemSelected, + isLessonNumberVisible = !isEduOne + ) + add(smallLesson) + } + } else if (it is Item.Additional) { + // If the user disabled showing additional lessons, they would've been filtered + // out already, so there's no need to check it again. + add(TimetableItem.Additional(it.additional)) } - - prevNum = it.number } } } - private fun List.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft { + private fun List.getTimeLeftForLesson(lesson: Timetable, index: Int): TimeLeft { val isShowTimeUntil = lesson.isShowTimeUntil(getPreviousLesson(index)) return TimeLeft( until = lesson.until.plusMinutes(1).takeIf { isShowTimeUntil }, @@ -284,11 +319,20 @@ class TimetablePresenter @Inject constructor( ) } - private fun List.getPreviousLesson(position: Int): Instant? { - return filter { it.isStudentPlan } - .getOrNull(position - 1 - filterIndexed { i, item -> i < position && !item.isStudentPlan }.size) + private fun List.getPreviousLesson(position: Int): Instant? { + val lessonAdditionalOffset = filterIndexed { i, item -> + i < position && item is Item.Additional + }.size + val lessonStudentPlanOffset = filterIndexed { i, item -> + i < position && !item.isStudentPlan + }.size + val lessonIndex = position - 1 - lessonAdditionalOffset - lessonStudentPlanOffset + + return filterIsInstance() + .filter { it.isStudentPlan } + .getOrNull(lessonIndex) ?.let { - if (!it.canceled && it.isStudentPlan) it.end + if (!it.lesson.canceled && it.isStudentPlan) it.lesson.end else null } } @@ -341,3 +385,7 @@ class TimetablePresenter @Inject constructor( super.onDetachView() } } + +private class EmptyComparator : Comparator { + override fun compare(o1: T, o2: T) = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt index faa833c20..bf6be56f6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -13,7 +13,11 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear +import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear +import io.github.wulkanowy.utils.openMaterialDatePicker import java.time.LocalDate import javax.inject.Inject @@ -132,8 +136,12 @@ class AdditionalLessonsFragment : binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE } - override fun showAddAdditionalLessonDialog() { - (activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance()) + override fun showAddAdditionalLessonDialog(currentDate: LocalDate) { + (activity as? MainActivity)?.showDialogFragment( + AdditionalLessonAddDialog.newInstance( + currentDate + ) + ) } override fun showDatePickerDialog(selectedDate: LocalDate) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt index d0a01b38c..16ec9746f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -1,14 +1,27 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint -import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.TimetableAdditional +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.onResourceNotLoading +import io.github.wulkanowy.data.onResourceSuccess 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.domain.timetable.IsStudentHasLessonsOnWeekendUseCase 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.nextOrSameSchoolDay +import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -22,11 +35,14 @@ class AdditionalLessonsPresenter @Inject constructor( errorHandler: ErrorHandler, private val semesterRepository: SemesterRepository, private val timetableRepository: TimetableRepository, + private val isStudentHasLessonsOnWeekendUseCase: IsStudentHasLessonsOnWeekendUseCase, private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay + private var isWeekendHasLessons: Boolean = false + lateinit var currentDate: LocalDate private set @@ -43,12 +59,18 @@ class AdditionalLessonsPresenter @Inject constructor( } fun onPreviousDay() { - loadData(currentDate.previousSchoolDay) + val date = if (isWeekendHasLessons) { + currentDate.minusDays(1) + } else currentDate.previousSchoolDay + loadData(date) reloadView() } fun onNextDay() { - loadData(currentDate.nextSchoolDay) + val date = if (isWeekendHasLessons) { + currentDate.plusDays(1) + } else currentDate.nextSchoolDay + loadData(date) reloadView() } @@ -57,7 +79,7 @@ class AdditionalLessonsPresenter @Inject constructor( } fun onAdditionalLessonAddButtonClicked() { - view?.showAddAdditionalLessonDialog() + view?.showAddAdditionalLessonDialog(currentDate) } fun onDateSet(year: Int, month: Int, day: Int) { @@ -131,6 +153,8 @@ class AdditionalLessonsPresenter @Inject constructor( flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) + + isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester, currentDate) timetableRepository.getTimetable( student = student, semester = semester, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt index 76d37b754..291c12172 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt @@ -36,7 +36,7 @@ interface AdditionalLessonsView : BaseView { fun showDatePickerDialog(selectedDate: LocalDate) - fun showAddAdditionalLessonDialog() + fun showAddAdditionalLessonDialog(currentDate: LocalDate) fun showSuccessMessage() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt index 9a84fb0a2..9470c910f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional.add import android.app.Dialog import android.os.Bundle import android.view.View +import androidx.core.os.bundleOf import androidx.core.widget.doOnTextChanged import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.timepicker.MaterialTimePicker @@ -26,10 +27,12 @@ class AdditionalLessonAddDialog : BaseDialogFragment lateinit var presenter: AdditionalLessonAddPresenter companion object { - fun newInstance() = AdditionalLessonAddDialog() + const val ARGUMENT_KEY = "additional_lesson_default_date" + fun newInstance(defaultDate: LocalDate) = AdditionalLessonAddDialog().apply { + arguments = bundleOf(ARGUMENT_KEY to defaultDate.toEpochDay()) + } } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return MaterialAlertDialogBuilder(requireContext(), theme) .setView( @@ -40,10 +43,13 @@ class AdditionalLessonAddDialog : BaseDialogFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + arguments?.getLong(ARGUMENT_KEY)?.let(LocalDate::ofEpochDay)?.let { + presenter.onDateSelected(it) + } presenter.onAttachView(this) } - override fun initView() { + override fun initView(selectedDate: LocalDate) { with(binding) { additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ -> additionalLessonDialogStart.isErrorEnabled = false @@ -53,6 +59,7 @@ class AdditionalLessonAddDialog : BaseDialogFragment additionalLessonDialogEnd.isErrorEnabled = false additionalLessonDialogEnd.error = null } + additionalLessonDialogDateEdit.setText(selectedDate.toFormattedString()) additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ -> additionalLessonDialogDate.isErrorEnabled = false additionalLessonDialogDate.error = null @@ -61,7 +68,6 @@ class AdditionalLessonAddDialog : BaseDialogFragment additionalLessonDialogContent.isErrorEnabled = false additionalLessonDialogContent.error = null } - additionalLessonDialogAdd.setOnClickListener { presenter.onAddAdditionalClicked( start = additionalLessonDialogStartEdit.text?.toString(), diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt index c207165d3..db59a2ab5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddPresenter.kt @@ -10,9 +10,12 @@ import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear import io.github.wulkanowy.utils.toLocalDate import kotlinx.coroutines.launch import timber.log.Timber -import java.time.* +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZonedDateTime import java.time.temporal.ChronoUnit -import java.util.* +import java.util.UUID import javax.inject.Inject class AdditionalLessonAddPresenter @Inject constructor( @@ -30,7 +33,7 @@ class AdditionalLessonAddPresenter @Inject constructor( override fun onAttachView(view: AdditionalLessonAddView) { super.onAttachView(view) - view.initView() + view.initView(selectedDate) Timber.i("AdditionalLesson details view was initialized") } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt index 0df53815b..8d9678e7b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddView.kt @@ -6,7 +6,7 @@ import java.time.LocalTime interface AdditionalLessonAddView : BaseView { - fun initView() + fun initView(selectedDate: LocalDate) fun closeDialog() diff --git a/app/src/main/res/layout/item_timetable_main_additional.xml b/app/src/main/res/layout/item_timetable_main_additional.xml new file mode 100644 index 000000000..b3aa55d42 --- /dev/null +++ b/app/src/main/res/layout/item_timetable_main_additional.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/preferences_defaults.xml b/app/src/main/res/values/preferences_defaults.xml index 2981e1845..2177b25f0 100644 --- a/app/src/main/res/values/preferences_defaults.xml +++ b/app/src/main/res/values/preferences_defaults.xml @@ -22,7 +22,8 @@ 0.33 0.33 true - false + true + below no alphabetic between diff --git a/app/src/main/res/values/preferences_keys.xml b/app/src/main/res/values/preferences_keys.xml index e87e9854a..ce8773081 100644 --- a/app/src/main/res/values/preferences_keys.xml +++ b/app/src/main/res/values/preferences_keys.xml @@ -31,6 +31,7 @@ grade_sorting_mode show_whole_class_plan show_groups_in_plan + show_additional_lessons timetable_show_gaps subjects_without_grades optional_arithmetic_average diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index b588ea5e1..e5020b533 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -152,6 +152,17 @@ before_and_between
+ + Don\'t show + Show inline + Show below regular lessons + + + none + inline + below + + Lucky number Unread messages diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8e159ec6..8c3825b4d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -195,6 +195,7 @@ Lesson + Additional lesson Room Group Hours @@ -731,6 +732,7 @@ Theme Grades expanding Show groups next to subjects + Show additional lessons Show empty tiles where there\'s no lesson Show chart list in class grades Show subjects without grades diff --git a/app/src/main/res/xml/scheme_preferences_appearance.xml b/app/src/main/res/xml/scheme_preferences_appearance.xml index 1ca4d2048..55f425d99 100644 --- a/app/src/main/res/xml/scheme_preferences_appearance.xml +++ b/app/src/main/res/xml/scheme_preferences_appearance.xml @@ -88,19 +88,18 @@
+ app:key="@string/pref_key_attendance_calculator" + app:title="@string/pref_attendance_calculator_appearance_view"> + app:title="@string/pref_attendance_target" + app:updatesContinuously="true" /> + From e1a19be06c306e26c5dac744a883788dd937b979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 1 May 2024 12:29:41 +0200 Subject: [PATCH 504/545] New Crowdin updates (#2530) --- .../main/res/values-cs/preferences_values.xml | 5 ++ app/src/main/res/values-cs/strings.xml | 2 + .../main/res/values-de/preferences_values.xml | 5 ++ app/src/main/res/values-de/strings.xml | 74 ++++++++++--------- .../main/res/values-pl/preferences_values.xml | 5 ++ app/src/main/res/values-pl/strings.xml | 2 + .../main/res/values-ru/preferences_values.xml | 5 ++ app/src/main/res/values-ru/strings.xml | 2 + .../main/res/values-sk/preferences_values.xml | 5 ++ app/src/main/res/values-sk/strings.xml | 2 + .../main/res/values-uk/preferences_values.xml | 5 ++ app/src/main/res/values-uk/strings.xml | 6 +- 12 files changed, 80 insertions(+), 38 deletions(-) diff --git a/app/src/main/res/values-cs/preferences_values.xml b/app/src/main/res/values-cs/preferences_values.xml index 5e488e40c..c9b2258fc 100644 --- a/app/src/main/res/values-cs/preferences_values.xml +++ b/app/src/main/res/values-cs/preferences_values.xml @@ -56,6 +56,11 @@ Pouze mezi lekcemi Před a mezi lekcemi + + Nezobrazovat + Zobrazit v řadě + Zobrazit pod pravidelnými hodinami + Šť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 7dfab1872..fe1da1b85 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -198,6 +198,7 @@ Lekce + Další lekce Učebna Skupina Hodiny @@ -747,6 +748,7 @@ Motiv Rozvíjení známek Zobrazit skupiny vedle předmětů + Zobrazit další lekce Zobrazit prázdné dlaždice, kde není žádná lekce Zobrazit seznam grafů v známkách třídy Zobrazit předměty bez známek diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 7130cdd47..23828b030 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -56,6 +56,11 @@ Only between lessons Before and between lessons + + Nicht zeigen + Inline anzeigen + Unterhalb der regulären Lektionen anzeigen + 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 4945d13ce..7ccae51ae 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -180,6 +180,7 @@ Lektion + Zusätzliche Lektion Klassenzimmer Gruppe Stunden @@ -198,8 +199,8 @@ Wechsel des Lehrers von %1$s zu %2$s Thema von %1$s zu %2$s wechseln - No lesson - No lessons + Keine Lektion + Keine Lektionen Änderung des Zeitplans @@ -244,9 +245,9 @@ Anwesenheitsrechner %1$d Über Ziel direkt am ziel - %1$d under target - %1$d/%2$d presences - No attendances recorded + %1$d Unter Ziel + %1$d/%2$d Präsenzen + Keine Anwesenheit verzeichnet Aus schulischen Gründen abwesend Entschuldigte Abwesenheit Unentschuldigtes Abwesenheit @@ -306,10 +307,10 @@ Weiterleiten Alle auswählen Alle abwählen - Restore from trash + Wiederherstellen aus dem Papierkorb In Papierkorb verschieben Dauerhaft löschen - Message restored successfully + Nachricht erfolgreich wiederhergestellt Nachricht erfolgreich gelöscht schüler Eltern @@ -347,10 +348,10 @@ %1$d ausgewählt Nachrichten gelöscht - Messages restored + Wiederhergestellte Nachrichten Postfach auswählen - Incognito mode is on - Thanks to incognito mode sender is not notified when you read the message + Inkognito-Modus ist aktiviert + Dank des Inkognito-Modus wird der Absender nicht benachrichtigt, wenn Sie die Nachricht lesen Keine Informationen über Eintragen Punkte @@ -647,13 +648,14 @@ Berechnete Durchschnittsoptionen Mittelwertberechnung durch App erzwingen Anwesendheit zeigen - Attendance target - Show subjects without any attendances - Attendance calculator sorting + Anwesenheitsziel + Lektion ohne Anwesenheit anzeigen + Anwesenheitsrechner Sortierung Thema Steigende Sorten Gruppen neben Schulfächen anzeigen - Show empty tiles where there\'s no lesson + Zusätzliche Lektionen anzeigen + Leere Kacheln anzeigen, wenn es keinen Lektionen gibt Liste der Diagramme in Klassenbewertungen anzeigen Schulfächer ohne Noten anzeigen Farbschema der Noten @@ -694,12 +696,12 @@ Wert des Minus Antwort mit Nachrichtenhistorie Arithmetisches Mittel anzeigen, wenn keine Gewichte angegeben sind - Incognito mode - Do not inform about reading the message + Inkognito-Modus + Nicht über das Lesen der Nachricht informieren Unterstützung Datenschutz-Bestimmungen Vereinbarungen - Show consent to data processing + Einwilligung zur Datenverarbeitung zeigen Anzeigen in der App anzeigen Einzelanzeige ansehen, um Projekt zu unterstützen Einwilligung in die Datenverarbeitung @@ -717,8 +719,8 @@ Dashboard Sichtbarkeit der Kacheln Schulbesuch - Attendance calculator - Settings + Anwesenheits-Rechner + Einstellungen Stundenplan Noten Berechneter Durchschnitt @@ -764,37 +766,37 @@ 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. - Invalid PESEL + Die Autorisierung wurde abgelehnt. Die vorgelegten Daten stimmen nicht mit denen des Sekretariats überein. + Ungültig PESEL PESEL - Authorize - Authorization completed successfully - Authorization - Dear Parent,

To authorize and ensure the security of data, we kindly ask you to enter below PESEL number of student %1$s. These details are essential for the proper assignment of access and protection of personal data in accordance with applicable regulations.

After entering the data, it will be verified to ensure that access to the VULCAN system is granted exclusively to authorized individuals. Should you have any doubts or problems, please contact the school diary administrator to clarify the situation.

We maintain the highest standards of personal data protection and ensure that all information provided is secure. Wulkanowy app does not store or process the PESEL number.

We remind you that providing full and accurate data is mandatory and necessary for the use of the VULCAN system.
- Skip for now + Autorisieren Sie + Autorisierung erfolgreich abgeschlossen + Autorisierung + Liebes Elternteil,

Um die Sicherheit der Daten zu gewährleisten, bitten wir Sie, die PESEL-Nummer des Schülers/der Schülerin anzugeben%1$sDiese Angaben sind für die ordnungsgemäße Zuweisung des Zugriffs und den Schutz der personenbezogenen Daten gemäß den geltenden Vorschriften unerlässlich.

Nach der Eingabe der Daten werden diese überprüft, um sicherzustellen, dass nur berechtigte Personen Zugang zum VULCAN-System erhalten. Wenn Sie Zweifel oder Probleme haben, wenden Sie sich bitte an den Administrator des Schülerkalenders, um die Situation zu klären.

Wir halten die höchsten Standards für den Schutz personenbezogener Daten ein und gewährleisten, dass alle bereitgestellten Informationen sicher sind. Die Wulkanowy-App speichert und verarbeitet die PESEL-Nummer nicht.

Wir erinnern Sie daran, dass die Angabe vollständiger und korrekter Daten obligatorisch und notwendig für die Nutzung des VULCAN-Systems ist.
+ Vorerst überspringen - VULCAN\'s website requires verification - Why am I seeing this?\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it - Verified successfully + VULCAN\'s Website erfordert Überprüfung + Warum sehe ich das?\nDie Website des Registers, von der Wulkanowy Daten herunterlädt, zeigt denselben Bildschirm wie oben an, so dass Wulkanowy ihn ebenfalls anzeigen muss, um Daten von dieser Website herunterladen zu können. Es gibt keinen Ausweg + Erfolgreich verifiziert Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr - This account is inactive. Try logging in again + Dieses Konto ist inaktiv. Versuchen Sie, sich erneut anzumelden Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal - Your password has expired or been changed. Please log in again + Ihr Passwort ist abgelaufen oder wurde geändert. Bitte melden Sie sich erneut an Passwortänderung für Registrierung erforderlich Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal - Captcha verification required + Captcha-Verifizierung erforderlich Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar Dieses Feld ist erforderlich - Mute - Unmute - You have muted this user - You have unmuted this user + Stumm + Stummschaltung aufheben + Sie haben diesen Benutzer stummgeschaltet + Sie haben die Stummschaltung dieses Benutzers aufgehoben
diff --git a/app/src/main/res/values-pl/preferences_values.xml b/app/src/main/res/values-pl/preferences_values.xml index 4df60b51f..8eafa1cb4 100644 --- a/app/src/main/res/values-pl/preferences_values.xml +++ b/app/src/main/res/values-pl/preferences_values.xml @@ -56,6 +56,11 @@ Tylko między lekcjami Przed i między lekcjami + + Nie pokazuj + Pokaż razem + Pokaż poniżej zwykłych lekcji + 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 212e442b9..acb1e601a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -198,6 +198,7 @@ Lekcja + Dodatkowa lekcja Sala Grupa Godziny @@ -747,6 +748,7 @@ Motyw Rozwijanie ocen Pokazuj grupę obok przedmiotu + Pokaż dodatkowe lekcje Pokazuj puste kafelki gdzie nie ma lekcji Pokazuj listę wykresów w ocenach klasy Pokazuj przedmioty bez ocen diff --git a/app/src/main/res/values-ru/preferences_values.xml b/app/src/main/res/values-ru/preferences_values.xml index 8d4bd8d7b..6c7a74aea 100644 --- a/app/src/main/res/values-ru/preferences_values.xml +++ b/app/src/main/res/values-ru/preferences_values.xml @@ -56,6 +56,11 @@ Only between lessons Before and between lessons + + Don\'t show + Show inline + Show below regular lessons + Счастливый номер Непрочитанные письма diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 87b458adc..3236b70a3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -198,6 +198,7 @@ Урок + Additional lesson Аудитория Группа Часы @@ -747,6 +748,7 @@ Тема Разворачивание оценок Показать группы рядом с темами + Show additional lessons 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 d78dd92da..b19caae7a 100644 --- a/app/src/main/res/values-sk/preferences_values.xml +++ b/app/src/main/res/values-sk/preferences_values.xml @@ -56,6 +56,11 @@ Iba medzi lekciami Pred a medzi lekciami + + Don\'t show + Show inline + Show below regular 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 9f145efac..4ce141234 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -198,6 +198,7 @@ Lekcia + Ďalšia lekcia Učebňa Skupina Hodiny @@ -747,6 +748,7 @@ Motív Rozvijanie známok Zobraziť skupiny vedľa predmetov + Zobraziť ďalšie lekcie Zobraziť prázdne dlaždice, kde nie je žiadne lekcie Zobraziť zoznam grafov v známkach triedy Zobraziť predmety bez známok diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index c32eedb96..72abe1f70 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -56,6 +56,11 @@ Тільки між уроками Перед і між уроками + + Не показувати + Показати у рядку + Показати нижче стандартних уроків + Щасливий номер Непрочитані листи diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f48732c3c..559ed753d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -198,6 +198,7 @@ Урок + Додатковий урок Аудиторія Група Години @@ -747,6 +748,7 @@ Тема Розгортання оцінок Показувати групи поруч з темами + Показати додаткові уроки Показувати порожні плитки там, де немає уроків Показувати діаграми в оцінках класу Показати предмети без оцінок @@ -811,8 +813,8 @@ Головна Видимість плиток Відвідуваність - Attendance calculator - Settings + Калькулятор відвідуваності + Налаштування Розклад Оцінки Розрахована середня оцінка From 71ab9586acbcb2e7efa36c7fed54231c714ad4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Wed, 1 May 2024 16:57:31 +0200 Subject: [PATCH 505/545] Add admin message to LoginStudentSelect and LoginSymbol (#2531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Pich --- .../data/db/entities/AdminMessage.kt | 4 ++ .../wulkanowy/data/enums/MessageType.kt | 2 + .../SafeMessageTypeEnumListSerializer.kt | 27 ++++++++++++++ .../LoginStudentSelectFragment.kt | 15 ++++++++ .../LoginStudentSelectPresenter.kt | 35 +++++++++++++++++- .../studentselect/LoginStudentSelectView.kt | 5 +++ .../login/symbol/LoginSymbolFragment.kt | 16 ++++++++ .../login/symbol/LoginSymbolPresenter.kt | 37 ++++++++++++++++++- .../modules/login/symbol/LoginSymbolView.kt | 5 +++ .../layout/fragment_login_student_select.xml | 14 ++++++- .../main/res/layout/fragment_login_symbol.xml | 14 ++++++- .../LoginStudentSelectPresenterTest.kt | 10 +++++ 12 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt 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 0c8f1a5d1..a8604c5c1 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 @@ -4,6 +4,8 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -34,6 +36,8 @@ data class AdminMessage( val priority: String, + @SerialName("messageTypes") + @Serializable(with = SafeMessageTypeEnumListSerializer::class) @ColumnInfo(name = "types", defaultValue = "[]") val types: List = emptyList(), 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 index 531684e4e..ecd8d916f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageType.kt @@ -4,6 +4,8 @@ enum class MessageType { GENERAL_MESSAGE, DASHBOARD_MESSAGE, LOGIN_MESSAGE, + LOGIN_STUDENT_SELECT_MESSAGE, + LOGIN_SYMBOL_MESSAGE, PASS_RESET_MESSAGE, ERROR_OVERRIDE, } diff --git a/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt new file mode 100644 index 000000000..a95eab807 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/serializers/SafeMessageTypeEnumListSerializer.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.data.serializers + +import io.github.wulkanowy.data.enums.MessageType +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@OptIn(ExperimentalSerializationApi::class) +object SafeMessageTypeEnumListSerializer : KSerializer> { + + private val serializer = ListSerializer(String.serializer()) + + override val descriptor = serializer.descriptor + + override fun serialize(encoder: Encoder, value: List) { + encoder.encodeNotNullMark() + serializer.serialize(encoder, value.map { it.name }) + } + + override fun deserialize(decoder: Decoder): List = + serializer.deserialize(decoder).mapNotNull { enumName -> + MessageType.entries.find { it.name == enumName } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index 0fe36aa99..4eb602658 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -6,10 +6,12 @@ import androidx.core.os.bundleOf import androidx.core.view.isVisible 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.FragmentLoginStudentSelectBinding 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.ui.modules.login.support.LoginSupportDialog @@ -111,6 +113,19 @@ class LoginStudentSelectFragment : LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } + override fun showAdminMessage(adminMessage: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginStudentSelectAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessage) + binding.loginStudentSelectAdminMessage.root.isVisible = adminMessage != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() 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 c81d353d4..39070cf0a 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 @@ -2,16 +2,23 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.dataOrNull +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.enums.MessageType +import io.github.wulkanowy.data.flatResourceFlow import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.pojos.RegisterStudent import io.github.wulkanowy.data.pojos.RegisterSymbol import io.github.wulkanowy.data.pojos.RegisterUnit import io.github.wulkanowy.data.pojos.RegisterUser +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SchoolsRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow +import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase import io.github.wulkanowy.sdk.scrapper.exception.StudentGraduateException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.services.sync.SyncManager @@ -33,6 +40,8 @@ class LoginStudentSelectPresenter @Inject constructor( private val syncManager: SyncManager, private val analytics: AnalyticsHelper, private val appInfo: AppInfo, + private val preferencesRepository: PreferencesRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null @@ -65,6 +74,7 @@ class LoginStudentSelectPresenter @Inject constructor( this.loginData = loginData this.registerUser = registerUser loadData() + loadAdminMessage() } private fun loadData() { @@ -88,7 +98,20 @@ class LoginStudentSelectPresenter @Inject constructor( refreshItems() } } - }.launch() + }.launch("load_data") + } + + private fun loadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = registerUser.scrapperBaseUrl.orEmpty(), + type = MessageType.LOGIN_STUDENT_SELECT_MESSAGE, + ) + } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch("load_admin_message") } private fun getStudentsWithCurrentlyActiveSemesters(): List { @@ -341,4 +364,14 @@ class LoginStudentSelectPresenter @Inject constructor( ) } } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt index b69700f17..4d0ef9e92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.studentselect +import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo @@ -25,4 +26,8 @@ interface LoginStudentSelectView : BaseView { fun openDiscordInvite() fun openEmail(supportInfo: LoginSupportInfo) + + fun showAdminMessage(adminMessage: AdminMessage?) + + fun openInternetBrowser(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt index 23ebffe9d..a813e7c00 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolFragment.kt @@ -9,13 +9,16 @@ import android.view.inputmethod.EditorInfo.IME_NULL import android.widget.ArrayAdapter import androidx.core.os.bundleOf import androidx.core.text.parseAsHtml +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.FragmentLoginSymbolBinding 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.ui.modules.login.support.LoginSupportDialog @@ -179,4 +182,17 @@ class LoginSymbolFragment : override fun openSupportDialog(supportInfo: LoginSupportInfo) { LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog") } + + override fun showAdminMessage(adminMessage: AdminMessage?) { + AdminMessageViewHolder( + binding = binding.loginSymbolAdminMessage, + onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed, + onAdminMessageClickListener = presenter::onAdminMessageSelected, + ).bind(adminMessage) + binding.loginSymbolAdminMessage.root.isVisible = adminMessage != null + } + + override fun openInternetBrowser(url: String) { + requireContext().openInternetBrowser(url) + } } 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 5c31f14d4..8de2994a7 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 @@ -2,10 +2,18 @@ package io.github.wulkanowy.ui.modules.login.symbol import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.dataOrNull +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.onResourceNotLoading import io.github.wulkanowy.data.pojos.RegisterUser +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.sdk.scrapper.getNormalizedSymbol import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.ui.base.BasePresenter @@ -21,7 +29,9 @@ import javax.inject.Inject class LoginSymbolPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: AnalyticsHelper + private val analytics: AnalyticsHelper, + private val preferencesRepository: PreferencesRepository, + private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase, ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null @@ -43,6 +53,21 @@ class LoginSymbolPresenter @Inject constructor( clearAndFocusSymbol() showSoftKeyboard() } + + loadAdminMessage() + } + + private fun loadAdminMessage() { + flatResourceFlow { + getAppropriateAdminMessageUseCase( + scrapperBaseUrl = loginData.baseUrl, + type = MessageType.LOGIN_SYMBOL_MESSAGE, + ) + } + .logResourceStatus("load login admin message") + .onResourceData { view?.showAdminMessage(it) } + .onResourceError { view?.showAdminMessage(null) } + .launch("load_admin_message") } fun onSymbolTextChanged() { @@ -166,4 +191,14 @@ class LoginSymbolPresenter @Inject constructor( ) ) } + + fun onAdminMessageSelected(url: String?) { + url?.let { view?.openInternetBrowser(it) } + } + + fun onAdminMessageDismissed(adminMessage: AdminMessage) { + preferencesRepository.dismissedAdminMessageIds += adminMessage.id + + view?.showAdminMessage(null) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt index ace12f780..2fc910242 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.symbol +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 @@ -44,4 +45,8 @@ interface LoginSymbolView : BaseView { fun openFaqPage() fun openSupportDialog(supportInfo: LoginSupportInfo) + + fun showAdminMessage(adminMessage: AdminMessage?) + + fun openInternetBrowser(url: String) } diff --git a/app/src/main/res/layout/fragment_login_student_select.xml b/app/src/main/res/layout/fragment_login_student_select.xml index 04c808857..6b603c9f0 100644 --- a/app/src/main/res/layout/fragment_login_student_select.xml +++ b/app/src/main/res/layout/fragment_login_student_select.xml @@ -11,6 +11,18 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + Date: Wed, 1 May 2024 18:50:57 +0200 Subject: [PATCH 506/545] Update screenshots (#2533) --- .../play/listings/pl-PL/full-description.txt | 2 +- .../graphics/phone-screenshots/1-start.jpg | Bin 349651 -> 188563 bytes .../pl-PL/graphics/phone-screenshots/2.jpg | Bin 455924 -> 267623 bytes .../phone-screenshots/3-timetable-dialog.jpg | Bin 371438 -> 232165 bytes .../graphics/phone-screenshots/4-exams.jpg | Bin 276565 -> 175412 bytes .../phone-screenshots/5-timetable-widget.jpg | Bin 480600 -> 257241 bytes .../phone-screenshots/6-class-grades.jpg | Bin 354200 -> 208968 bytes .../phone-screenshots/7-account-switcher.jpg | Bin 300110 -> 193797 bytes .../graphics/phone-screenshots/8-themes.jpg | Bin 424342 -> 257099 bytes 9 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/play/listings/pl-PL/full-description.txt b/app/src/main/play/listings/pl-PL/full-description.txt index 7da51da2d..b0193b5d1 100644 --- a/app/src/main/play/listings/pl-PL/full-description.txt +++ b/app/src/main/play/listings/pl-PL/full-description.txt @@ -6,7 +6,7 @@ Wyróżnione cechy i funkcje: - szczęśliwy numerek, - podgląd lekcji dodatkowych i zrealizowanych, - ciemny motyw. -- brak reklam, +- opcjonalne reklam, - tryb offline, - powiadomienia. diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-start.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/1-start.jpg index 0ed20c04dccf9da83e1457c1eb57bec7bbe6a036..19b96d9a6a32df8ad4b497cf9c55e9b519f5e45b 100644 GIT binary patch literal 188563 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�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 edG~SGZ$(*4HtB8aeQM{G?Qd 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[x93~l_96*U-4kNh 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%0A# z8Y|eZ6rMEc?NYORU$r>u+Ok)x_N8bGu9_Mua%}TK#Ldto*n4 zkKg;>>*pq#-j9wv{`{kVW9$8=KUA_y`z~grt8Tl?YE>0+Ztv~#@6)&3Ue+%rb@ff? z8<*E*E2me?KA>S^eNM9Pe)PZU{N$9sWhzhVi+*0-zyFuOr*%igEe`!>;GLONuunC5 z*YX;rGyDDZBp$7eRob<#Gqh&e_EY`)ud5%tUO!{Q-u6}Ze%$9>uYXSWu?^QBsl9u9 zf;gWqonhwTw=1XV-R~}{`TcioviOO0{*Tkr-igiq&yXHf zKP~yA*Ntg=Kblr|YU|&csA?C>E?O*@t>xL1=-?dn-(oRpB#QKYu(8o0&~mr zVm>|(-6zf1dhoB@o#2~->z6*65DPE6o-AK6Z(7Bxm1&bC7gl-7fIMg^UQ(o0>gxKc zODRn~!flu4PM?c0*}})eVFzJA%;HFm~*EzM_HnFGX!0IbMx-|()eSoMWro7>jRLQZ=14HB>k+ zQ}W#EAF(^%`_3-;&}Q^5wR`X5nE7V!lW!zTN(WBdxHhX?IXK1EAFq%qh%GtD4%jT4rY~SmE!oaq<534}&KAgdUTd61y||$g1Q`aja!?l0;9R*|w$e z^!^CBHGizjRjQp{r+)x-()>q9AGK)+xPC9*y(8@#wGX9J~jVPxi9(9 z8u^>kg3fEadRW!(eyC30pZ)UIW3N(n#HK7;x$<^Kwe3~qFRSu8uYZu6W^ngh>FTb_ zCg-d_&DfqEv3&WdbKKiF7#JAjjSM##UfgS*Juxu&UeIKz)xsv{Hne>HW9H3!RdxG8 zHwI=p&u)R@)1veV+2nuzTy$lOI-o{TZn@Kl9e5HRXGn<_4vF zTCl9THvQK*fB%Ix$M?zeFK>+&pZ#3A>c-)7?`D2?^2m-lwm7PzqjQr|xnk9|Wu^D^ zFRen2Z=2o!EVECnl=UrG?ruFb$THAo$Ms8Hl`&g=Ph1MG@JYQRTOIcN>_rWImE+nQ zXLrBQ7M{8HeE>a&1~S-p(;ieRcchBRi!w*%I{nMCo+ziv`)LoIwrr(V@^U@$EZ(Y-@eVZ&(Ql?(Aov_I5 zX!Z6m!*lOkZ=ZaxXRssdW4Y2*schREyUMwVKkb`V%XfCp(wxz&c`3c-{<1V?|#X;WPi2&Z2Kn^ z{nmXdXS`>_mT=KbQtqk2c1tvpr4Jx^*E33cL1o|W(AZ2v>8Z0OeyeyisdT5W zPR5goF}+XUJg)npk29LJs_SdV&5n6e zh1XV9oz1#nbKEIwZOF{hQ)k0|A1OM?bUSUDOP1SO_u{gNoAcvl=kA}AY#?r6ELfCO z`szQ!7Ma;k@1{LZ6MhXS{km3YsX zHv6`Y$EnlJ(c#gX^xpZandk4HG{1ZO;g?2c1y?TaTl?wqXP#&8w4-!?nEbAeoLF)Q zuf1rJjK_ohMMH#weXbO%WNkgMa=+^jFQ05FRoCLY6|3eMtlR!Odd>D(yQ6n4wOMs- z-S2nzjoyFANz4o07;@>Qh4;tKjlMTtwmUSzq6cm|=i?^(9M}FIs^#CyXXZ?QIpv%G z#s!~e{)^dt+x@fC<-*6`*e1U_D*w;edS0#YdA{JY>DNDg*ZOXHD6@Yl+z2lS)_|(<^8SXvVe=qaQclCc0!|#4SJmvH5udefNxqrGcwKkA{ z<2=*v<^LIuKGpx}_*vxJ@1-k0JAYmJpW)_zhO1spQj=dOKuyC#vAMTDEWPO)q(7Bc zB>U^lZ~xkUg?!(fmUn()&zWNF3Ms2s^8`QohFOd5mtT;|x=$oIc=g=v;kz%bN_l&G z*_}*YWznvk-8;?o{2zSbcin*9BL1-ZolnhET(>?Bk2(^jd@=LIlgTIJo_-3fs($iv zYteeKne&Y`ex7&PKQ~bBX7Sa8oqI*J1EbsHH6OXUgrClv+3Ol#8@(}dW10C` z;}=dnmzj&oGWNQhpS0q~9^S^6qe12;6cORYD-}$a9%(6deb4DcjRc`8apRQ4Z^;5W+8D2M zJ}ge>>J`1L#i3f6_k|X%%IkcpR2uU7gW#&J6(1M7@0}CcZvT5~e#fz7%}Xm*uigEr?BCs4YtFm8e(An+axy4L_>&H{J^h({ z%_drOPh8y9kg(e?Qdfk8xP-nqw!TQK&iLBh+0*OtX3jftHMRRpK?TQk@v>QVUjzNO zCEnfha@vx|Q|?Jlc=^pcd-=_CZ?khv1&q^{y^BlVD7>gN-cjqr^|i;gUNpQd^zm?d zWt!329y_JG#aDex&b)s!&7{I8fq|jgj7Q(_)Sb0QH(H;47+iblzN4`lpyBd5+okI1Yy2+ z`~%Nt-PW%X>xAp}9F<>RyYIHCbM%KbxgmZ}ZP(t|U1qx7=X{FK#vglbGd9dvE?(DN zY@f9%^V=D#?QeoLk1qcZGhb@gEw#JCo7XR%H23x@rMH_eith}pV|6mujoE4y?sGgR z;K8oz=@#THk#3Y(87YI63mm;h;}a`8*pkpZOLqzbU?T@s=w-XQ!I` zYxab^nj7$aF>l?IY*%61i8^u0)~5}o<|G+{uIXF7O#Qmv*56F$3e#3^sxkVV;gXyZ zx_-N_U&qyQE$h=+CE+EmU%n_89lafAYb?9b;On7hJ9x_8=>F3Fy)`&|`l9rO59Pc+ zaQD8#cX0!uO^yr<4Ao^m@}?(u+b=w0_r3J(#MAN(x1Y|RE48XGYxbbz- z(cAM>jcq4vJo2nUMr`lrsxFhqTSMHp#Vrl%%lgV{Gj-~%@EO4eg^E3OwplGR?0gv- zmeaoE!AFr}oAcJYtkwP0t-d=XFkd_?txD;su-!Fv6XRfCRa+PTu828nCYf)IdS)q} z3hG^VKhu4trtX>ga@DMtT`T7%#j^@AY`M35`pvBW4BvO^f1g!sd-6ZSlF;w9AD-I0 z`x}`4ZQ`G;s_`MtZ_;~f@BU}_Sn2&QpysIUzL!}w7yf3}f2;o*w4iOtFHq+Ol&$gL zlLmXNR%@=kd*|Xsr$y4uzl_3Y5sqPjUnd!^>S-W(d9xAMwV^W5uJ!6E*0AJ;Q>9}eDLcQxdA z%waaoPPxuD9~H&{zpT2e zo*Fpg(%$mo@+Qr6-^=^ggnre-~XbiU9s9c-7Bl&(I=VX%r1BKXC)~wGnvtM zSFI%Z*U=?mHJ46?mTkM>&iZSbxi`;7^Ql>7zSqnbRXvT}<{GzRcj@io)8Z}5z8Z%b zJ~Wb-WG_;kAcy)RCi4|)5-e`9(h;+ZGHz(k@Nyn?%L2LKL?Ou1r z<<0eVxI;Qe%$ftv}RnFCVu;x+5m}MRnk+R}1^6&HHfd zx^c?Ob8j!X{HVOO?mt5&Pn_NT;=RV#_wM`TEq%W-*Ti4SM&8_gb@@!mX})u(X4Ywc zNu3+DrpW!Q-_J=|>*iXERr|>8E9Xvgf5+J-ugKyGEt<;;rJ# zbJfn3|7YM=|1!^V>eTwDCA_uk!@IkuUhoAstG*E*>gwCujC zexkq6e}+ht+e)kU+Puxut}54BAJ?&B4V&2PU3b>ED4)9X?OnXcLxampOWiix8p`?W zeEp&$=Q!ix3{@S&`*~l=PF&p|*(iJ}Z27C?B<-of_k4Typ1$_Rz17O{nXvw`XH(DI zSd=|)=8rS)zV6ZTExpurvRa~;BkJw;dr_H>@;}Wx;{VcSv2;?oYuwep=e(a?dz_VX zepcrEl+!C$$2)w_wb+w#IVdXcZm;%C@33>8qMLT79Y3vH_3OE5nX7WwyU@s0J1@oU zWUr4nvoQ zxqs`l#8*e|J(Sz|q9gi@viF9w77ILAyi}}>$$2*KX-LPW_5PZHr!SfPKDjBUbKmPL zH*_AZefYRsZoB-M&wKI;i@lzerMDU${y1Of=az!&uluG(r$=S=9Gm&x_hQ&Q$@F~J z+t=NWo7PKuZQQ0gS8e&ybw1Coue`PF%+F`aSB-_fy^(B_oEvmnw(p~BasTAk7iu?| zmVLdrQ}D#xvRAY7FMQozcK64J^w2(S{bNr{kKA9BF|YH7Pu|y6$EB)xT^(Dk?GJ10 z=BlpsR@rgUWu8k;`NUfrZBFzR2L@Ux>HQTHIhbxCvFv);b{UJqt3KzRn)UASKL2A^ z*IX6e9P)8y?=Sz^EkCD}T%CJ^fq{GWtHS!%H^2WC-Tv#9@8rpQ@+$Yf(>^bL_QCJG z`U_L?W0aCBr|pZ|YHi^0D#ui3**xxOnH5t-Go{R2r{_+eDBAHZBwjOa_nVFtuYY}- zrJwX_fAP_~yH7vO+L2daXj!iQHnqa`dUBT9zE!2y%xZRjxxPrb?C-}}aX(rgKKGG3 z6SdxKb97L5MNr#qUzIn@Z}088m1eo|?Tgr5(@mdA*qHw)4sn|+_`_gEp2w$d>&~MW zlU8lp{x;N5*e*7E#>}_XH^jDQx-Iv8u*0n|e`jdX>OZ2P7ecfHLq$*Wthl~P?QW>_ z<&t=-{MX6S?MKc{Th%!~eo>g@dbQ%5NptswUEZ@IdiKmZ*^{3wcKU$b)~nvR;eAGmN+%~Xt3$+UhQw+{zU7m zSXTC&cpscB8FRgT?q%V9nV&A(`OSD}E9LiX+jDRcwX*Z0FZVS|i7z+L7U`YpnwRQo zauG3P?fr6;Fdn*(3Shigxqcsx|( zO6}t5wX?3y?Qz$7d#C&UrpgPSx6Vw|58doqxPG4P>NE99#ecS4xpyRS)y+O*Vc)2- zH)mda-nza`*Xo*fX~_F+!8bpdFWSd_R7K73J(w`(rbn)T+(MdRK6-_E}{A2xf}qo~VgH=O=AKh<2uT7Aif>ZzMj zyTTT|yRLS7>)Uwin5*tezeIVpzRvpaUF~j7;+JcYGZR18s7lv|#q8;zgfQ*UvNy?J|vzx^mZY{`*hc*S)&4FLIin zQQ7OeE>(9vPnX%gd~%7KIJaP!(u(W#T*>RIo>eKIx8BmZ?@HNSor^M&=a)4d?YOD% zG-uM@GmYzuls+w<_w?Ds*Vl8e#_apK&DG2x?0crKT|2|hYSX7?+Fr|cUr%3L^YZ!< zw-s zirB~_kw@Nd5Ao|^OP-uoY2MhoHFU@7b>{wlvp&q*Yw99X@waiw?~U&N8G4RCpYQVd zaN)DfuYdfWT0QHb$?;1K3=ENSjCJRVYY(mdcS+cEVy#xS`kiNUUe`LyZ&`jb=+dj{ zJlk}`uXXcPPh3(InfvW%)4qx8!gg)ScpC0>J@?yekAJE6OWjQO)n2ylI-PMeDRXyx zx8OdjUvpL~KVAE$U1jg|hudRUt<7Bh=E$+BK9%Keb2r=aCw)D#pYK+E=okI)dADB5 zI?Zhgj$Wy@qKhwX&g{#_o|)&pi~XggmAgyxWn60W;q%3_9+|wlRy$$S;UfJhm0M-M zy2-8Dw*7U;WuJ3qOJD zMar{w+r@Qt{!J6MI(F+wpsU!bYZiZG?PuHhJT9vKG5crh%0+9HW?WxpI&-d?xj)B) z)id^)R-M@-U8mc3(Q4h(;<;PCeAvF+c>1NiCpu=YTJ^=nHT0c9+1jgBJGQ%I8@;M{ zliRbKH%?zT?5v2}X8ZfAtWr1i@;waqH)*=-Td*VgW&TbpQ+eAle-Kdnu(} zZoWQl_bR1N%U(8}dh<}TbH=rG`}k5+S{vgZ@gHf?wkDgvP|i#hqAH8 zZ=dW5cp;}6^E>x>(A(94($~y4^;xbK?DP8)+Su@E7!PlOpw`{dG4VTMJ zzp?RPk@6IY--Tu8#MY|6^$GrSnLnQO+FO;YlCk-RK4+JnTY4rm%6##{xo=+eg|vlj zedlts{N}2TuGwEg?q51)&fj%6YsJi0e^k?F6{TC8c)Tk)C)A~O?PaOIuhNoiX8-Mw z?&cFQyuHpcex+4zdfV;tZ7#o7{jOEXIFX&0AHH{uU+>hFyZ21_G*Q$nJZG6Y_^^S! zX(lrs%hq?-KM~hAG&3|wojJEScGokdS<6air7u#e%A6&+k-R=ciRYE4chwky7u9lCKIljhECePW%|ip!M8<9rV?GV_3rwf*W1{# zoZt7wGV_l!U%!0xbM>^y^&0u<;+sxS&zjNsA#-s==d$w4yXJo0?PvAUR_E0Bqwf4W z{&b7ht%!PB9Dic9)5+}M+U@Tf`(6F(cb|#5J}cqv$DMMy|Bi|Co`?%fdK_I5wfKFe z|7Rb&jr%lBxOH|IT~EKVy1wPaw`0eyIonFvp1Yso?sv>IwCwhx@2k|Smc6c=C-mmc z<(TZJ*KAiMTk@acVcogAnmcUbx3pP%L(ZK4{$%>KodtheUx(QT`xk7#929?3cu#zJ zO3kLDO6rp)X_Zd0{iPjVH+xmiZq1lHrfX)MoxNHw=D%O)=To#<`s5l{)96)c*Y=6Z z3d>rRhF&#%eobog?x@#m%A-!}UYdI$Ec%to(iNR|Kc1GJx-RPvS65qStg&mbZ%pFX zIJv)jLRaPAnwz;wDXHpFa>Q4wC2^;HUscvlTmPS-deVP}%H_s)=KpeyFTc+heD3{K zvHDE;(?OwMUF?okT)%t&KZE#`{ZHA?1mAu)ZTVULYt#QTr2l6K)#AwVUqEm=nSE*B zjaNm|Tg*$ct%6AxX< z(mT=h^vk+?#rm#-u7*CA@09*l%${7f&*+_S1x9C|fq_Bxw(hLQ6Yj3enKmokKRi;& zRap4k>gVxJN_ngHrML!#1e$AgWj@yD@l(4ebT<6G-;~Qzr=BH9=r(&7mNu?$-Ly^b z-qW~$a~IE=y)!g?XYcDxm6Pv>hTeD+v6$Cw-ps<9>n2CiXRKZOD|@NQ*X;N6rY2wG z^;vNxr#E$Z&BR+8QagZg&+n_HoHR ze)`r!joDxOYw~kI%OO^2$8>L6w0thpWI@9v>(kgP=KW`Ay{T{N_iWy}Hc7SeWqu*g z;t%bf?b`M1>8`AKGYV?1^ZN-)mz|Z-TvhVsdeOv`fK9U%c3wU<+equ=j4R(KzUs&} zSN3{lv+i;8s_etJS5;;9Cq+K2R{vNQ8QF7Q?_%tNw@O*-|9pvF@88<>pW(^E38(XN z?#xnOy5iG)*1H>~JX+cN#qZzAKP6u_-&5Z>Un+6RO%cQQrt=nBUKReA7CYedpCN74ri8np$rfQpcWZgCOAVBtYCWlGW~kMt z?dcvX-)ntR|E-n!>2#FKoq5MCo?f%g+FhZQ-rj0=;`^_lF#g%K!l{Ox@wm zqx*U5-1*9r@B3Y{6#u*E+u3(7i*{8lUGuCmzcSI_`j-1eGiScN9`V(uO7wNd9RC-^ z0ctxP#dUSx&XYLm^77XEEh|5Ku8e;>qvz1|;@w+i)Tyv^j!1}bq$t&+&+2Z z5g`Uf;}>ibk53J)6@N0N+>tb1o_ywlIw@89ju zS`q)a(Du;WSv8qiH8W2g?Z)t8>BEbN^< zW0t{0>y^Ff+v)`0Ce87FKK1KNp@c7?{(WstW_D|PiWd1AUzF*1?`68`?&M3WpS#?; zZ@Aw$zNk;&bdkRH_I%#^SC4HCT{q98_xA4ecq0MBwNGC>zW#ceVdav2x3pw=LuERZ zMa5If?!Jxp4mEf6x6;%uy$YVy+jjI}ZgS4m_PtVCe)~_EWxeeFwru7@!?y*0OkaK4Q!1W0pX+X+T~+#r%&TXMW!4sl z%7AiyNbsC}r#r9L*fT9)(92(W^gqM;^!s1J?!R7X=I8ejbldBCn|Sl)w>$qcIC(#= z56_FLTwXHcn03rA?{%R%6LdaWMyrHOT%>dAs(;kF@Qt&7$^6~5>}BoTq*sS;c4^F2 zTc7S7@$B^EZ<}jQtqD_`ySgk%-SeTTfA{*d$+jDLi)UrO6i>TZyyNCNk?+~QIbTY* zOft*RQmESGG4JYKnd|ByA*#Nifrg>ROAep<+g&lcG;*&`sBhqsBkw!=wX^QnWR>jN zx#U~UzTFe`ysihu1a@w!@bNzNT&Z&B3~6jk-1amWsqqwS79_?u;wGlV^qfEv@HSJF%W~)pM~$ z*Yws}mH(<2uYLB~V70-MzjJq9%YE&>xc$Q!=MxXB8JAzl%{`v8og*l^R%PAJFvF9( zV?{X6^@e<2Jg+C~_qpKnIvXW=LQT$d+_y`8u4f#zsiyDm@8YGUZf9+`=3PH4b$;fC za|)%lw_KNo&p9@GY94QLX71VI%86M^s7m!ty&&A{hFCh)S+9;&dy4jmD#JLT;gK2B<}R@1M9ZlEskcboAu=DrO@k- zFKk+L-)7yjNnWZUSsPES-t{dnY?1z}xGTSdb59y>ja!rxZgtki+MfOVoEJq|*P=TX z8y~v7+4bg(Rmv^l$K!vx?fRLNzO?Jt!XLcG(!E)^GBT4+e2-i`ulL2jZWXS5zm$At z|GYfa=6Jh?j(c;Rwff!nsg;Y)X3g9`^}6-q!^>}m|Fzq<)nwc4SL?n@MThd#d^O#? zX`a}>mG6s8SN^!ln>Qth=(C zml{6nRgyY%?QhD7uggtWomh9(Xw!Y^yRz$=XTL6)^82So=biN-ub!8$`hB8fmYum| zoY9->;d~vh+OO2ieDX|sa)ngEwIs9WwWTFj!w*?0FS6Nn=J?Xt$^J5eTFV8#nYg}R zvhMyT#JqYj&Uy7vv{|uDHcWr#JOf>>xI`^m=~u`%|D9(oul;JX&-CB4blT(WfcFw| zeY&b`i=wjj^zEFsJ;d!@xGU4f_tR59YfqdrpZQMO<~6Qi2Dbu_t-N@qa?2^5XFIRm zQ!-t4)i`eQWve6Zi({*kq;8mOzpNn6eC6|j%-8RKiY>nGaDCTRk+NItmgY~Vg@k)O z`J!dGCjX$)Y%#|qt#zv}U)*K;&ivf7?9er~J=;1zKFeCY$vjwYZu(iC$^CN^ci(up z^`)I{;k3!eN>0a@)#!)q@+vK!DWkRW+q$s5G4o2?mKUwk-&ftes-wqNc-ijfn^*Qe zTRr(&VQ9?Rk0x(({LV^!3Eh>p?#4AGtL8@45yr zkDHTxepzqGE8Q-c_yD7vthswOM&*lcFq~Yk0P$CuU?cDHhf*1Jo(Y8 zc*4&$px$YR*J`cGhej-^6u#CrMIqYoc{f4-|He9iLd9sAD#Q0f9ie3htWp=PH#BFElJ9V>O;nWWrw%My>=FR((r0-g! z_0?^aUa{#J>vQ*x%GrGQR9VLtdqU6SyvE1ckDdLX1MT*u%ks-`~2&h4I8n0D9n?2Tn+%X?n#D|tOVruzbO#vSFS z;XKW?q3SeSRV^x~)W$Eu^g*LzR7@>%1uc(LF13o@NM z%`NR)X6-vRuXDzCWu~KB*2=GZX_9mG_Y=LcbCYFeM$MbMD)all&DInA|0%8b&yc@Q zX8qh#X+_nh?yfifMO|9IO4%g$@|>i(u}}AXTc1@GxWQKZpUR)q{|p-Hf25R8zsst* zaM^fflGclN^qd*b4c-+iv`yKD8w z#QbXUWd6@vj~G?)s+@dv^H#{dX_Nm(%B_04?Te}SFaN57>FRyY^xkV2YVOwpYGz-FzkBvYs=dpfJlkZ}>)KK~y{&$LmelW+>%9?Ncj;-&+sn&N9l2RAXmRl3 zW$ntRa>|QleEk!ne{puwiEi0fE7!c-rIQ;P?;mgeNcmpL*S*S9rkC7z$-Z^Ea8K0L zo?t8PlC1TUQzwOgt^UuzKhyTnv#YCe-6kAMqlz z`3t#&QpInjc%MCH^5({u*WOHD-F}}hulgmN^5y;90}rdGT9+2i51#yKz3oq@_fzs>i~+ z))!^o$L#)cbN9;k*}P@nekm6V&q%M3*?jOw_`_1SpI_Yh?K;)&AL?^g+I(zh?6$e$ zb@l9Ro;z!$i;MD~eW?@rG-ut;kE`0YmQSz9Dt#BWYTnI`Z&_dVEm{9}LH*76e-ZOf z#=rksr=5TL{a^c!{~3NQmkB#^Cs}&iE*3OgcIC{=8X13FhtG9uqFhmAqlg!CI4SqrCgYP;HPVX~ZVvZ}Ta>&g@vy=7ywo47^6I9F zR;$-kPYGIeJKDW?)_D`Y4?EOLjUT(bzNNe~bU)wAjjPWZi{_jcuUPa|T-Wtg`oS63 z=vQ_ssDct-q5WRL$rSeYxmAL+dZU z&Qo(X&2bAi%rd`qUv}cI| zbj!V?1CL3Cs~G-$KErj@@`F2fpTGF}?(?u*`{Us{XLY{uxt5fM`gl$h{+GEgr29|X zjzc%L-^~klUY>U2+=qj)M@o+P&E34qwo2!_z)a=Bw`Qvsx4t;}?tLyxM)#)0;%nyG z%Jhc)XV}t~FJHX#=8V3%=VDLl$k#qAxgO4Cqxts5*K_+)9ZkO{dRqyvF1aBZ^G*8q zoN3uDo%%T)tF~Ou-EMdCYSfC3?(HE@UHvWVn~u76-dNwVRWBq(G;NEUGXLi($7Yot z56>wnTeI}s+4d~iS!Q=KGxE)TPM#+z^`uDcKSR#(t>sz2--ZMmhl+g^d4HcTJz2Qt zl>V99Gq=YS_V@V9+bI57-&$o^`KsXb=LhAXUQth;C#_$ol9ku_Ve5U){@v4D-fy>? z_PF8c*^-xjX7~0*mHRKMGicg0$64vHo_E%Xx08Z=uSTrCI%)e?pQ4hY8u|8A!}8ql zw6(JyyZvlDV{o%~-Rrj1S+<3@W%*@3$1$W?#V#+s)jR2UPWL=5soJIMR=9k#*#6G- zZ}7Gd|3iCEEIlU8Z_)Lw$|@q~v-$SP50|}pzp6Y~w)jKee0Q&#DLdJ$m(EDKwu{Zt z-BCDU$M`A0rFhaH!pE<&_eSTnRm&r1zUZ4d|NSbF z!)hn>X6AlcudwIbrYl$X#_V62Tz9qC-#F0B#lPaZUd*bu!Lc!^w>CsrM{tWgd~gKpC_3*&;Xn?Ad~tm=`ipun@e z8OLTl3HR#0`dB8a>(=(qXZ71c%x9L}-nrhzuVe0susyP~r}~Bl@2lC=COxHcp3qe@ zox7K2Rk)qC`%$uQ`CsMlsz3jRzvTGdCjaKw^GDzRtT|^N`^V%zgXdTKm-0LH&%fI5 zVSSkYTW#@2du`+X>-D_$m(Je0#m&1^Qo347yVUHg@6}4B-o3Zihu#!ExAfdA%}>tk z#>Jx5k@rJ`U(d{(l^K?~WM**c#8YqgO0PWotaNUC=4HRLhW^117CA5w==M1`UzPgL zFzrMAgQ@pF9ts9u9=+2peP6QpZTC$}ElrQ7GwhFBUYl|$bD6ip(|a2Fb5{#rSiihi z{2zAk=cnzbTR#`AOV`}%?h-RU*5r@OyydMtL9goE3ybo8RSO-tUnw6bwCX>@ z2ldA?^Or||^`Cs7t9W-==BYQ)eZs}@uBJ6X{;O0AXX)A>UF%!5B-Ff9y{anABBt~2 zs_A9cq*U(`~`{>vX+bS7(7!E`mN`DR6CXU#wVQ5CJ-G2iV!Lve^&)2}N` z6%YDv3nu^LjQxA}S+we}RNKjKGHjmJPuKf)@y~@VcMx+`=fHDSQ$9z2{Zo5t@7aP` z$uAuk7_NQC!dl>qkA(L~~iG5RTC%-H9E8VxS|5osum8MmTOG7{&SMJi+AeiEPs3LaXA0A+xxXElc%nIx$Ebir*Hr4 z=j~jd_txrs&Y!4bp{M>lS2_HB9n-g?QfFRnc6mR~`q|9+(XzjKe&qZ;`=8-$*MEk~ z^X!!$>^r`$PG|aa=_&VL)i>S$A|E)PcmB@(e>cYeD*R-}{if>qqq@SKzWnSk^C!)y{&IHZ&e&qV*;2yO-X%0NZg<`~|LU*DV%z>RaLrA!-&;eNtb7g$;MF_f?Phc`kqAE3@GE8L6eGLko9!o>!h|mMk?_T3YyR z#Fd+Zww%x7mQAetThTqa=*j!(S1jWq+fII%lCWXQOIO3^^LEbLInU^WznBBF(dD_a zS9-%&m+EnMhsG#(zBsvRddACzvDB4K54mk$m=&>Hq1P( zvFS`(@0!{h%hFp4S5<5F{;QKMny(YLyl?Tc0H=>QeQOms#}OyXd?$gKyfaw|kY&_c=E&*7TE6{-^x^3~#6Z zXE=EOKZBL+e+KpX&$jX1;y)k%XSi7Zss7KkbeAn#XPGZAkr2+0GmFwppL%s3?}NXK zPHNA8Z8?A2i6{OsyWj5jdu-lx==`B2OkoId%K&Z#|zGH%@J=rz6WtEAjF z@%F)Wdg@Q#ZvAau?>F7tc=@k2S@m}^XU=)uz4O*D|A``RK2>wdzx{URl)g@M(18zz z1t07bbie%hT~WGg+x&dLi{EC~f8YC`fpPjtt;^zq*M*+Xd4B8NoO$ckZw@tnefi!r z+rZP4woPZ*y!Eciq=!LY?(d$ekX=>3*Fo z*@}gNiv^8M{WkrA?V?Av7s6KSlPlT$qVL(vxu0grW^X_FFI)QZQkR#{Z=btX@4e06 zXyL>IiMyA^URoz`X=m)b^116iZM2h(Nnd4W{N}T2Z{Agj58V6>(W$QcVrHye=JINT z@y@i6@4FV)YQJ;|`p+QU`A#I$zgO3La-__&8+|X!><=yOpH{Wc&hJKuq{^gBS)KL8 z_r8W(o{PPDYVWJn&x7M1y)hK@$&`^-49j0U!$ z_4H&8+`IVOr{t`1Q$fM1pA+pF=kBfQF_jK_@-r{Cm?N?|II9x9|5<-?)|6(qiwOwKjXXe3jyLjW5yf2v$fuxZ)BhMjkHj`+JhmwTGWz1LW4^)mDDhn2r-tN*+)+Rn6m<)bvy z2>Y|L!ujj-?pn^8v-=OXk<$7@OSjzUJoeM#KSNzv*1hadnHh8ciS0dqdH>JYKFv*A zmTtLS@T#(G($;^HL08^;%-C%xelI+4{~Mt*?_T;u_sxB8=lsCzeR%IG^WX=^b3T4? z-x|GWnny~?g}rBL^4v|&#!g)|_f(sdy6?NGp@t#8#s@16a@wZb9(?<~bJj_rebYCu z?mv4=Nn17VUhl>GjR7~`-0A+dXywa-J?A;&Li^U8wceHLLyuG{b%4?SwTaG?3*zu~Y z(CpE=8~2~hGkoSNAuhc2?8>&g^Ml-V_~Sx%1RGC%bN}3r%ZrvTjox2H}wqHoSO_BmB7ebY`)@A59wODkUY&3OCj(~93WUfg?e?`rm}RbAcpy0Z12 z3JXge-k!1Lj{bG4Y}-{cuZorysRX*+x1RoM^10Q|TtZb{g<}JKZBn0h9Em=3Yt`4A zmu4kLPQ7&bkJiOy7Tvlpbu?bw*ecF-yi!y7qIvPu@SQ6qa-WSzeJ)EcXkD=)uNbJ@r7ebZTo)v_Yqm*-#Bo_y)O`9d?l zzuyl%f=|@WJ(+pR;GX=s(8vC}tZj>*xj$W;$gp#kV8p4rnj0-w%|3E6v8YOPv#tH= z)IVxR8|SVT*|A?M_w1fqU;Oa8OcBlOST%3Q_d~bmo)(S^PJ2_=d*bWm@Pj(P7M6LwHaRZ8&*^Bf z?)G`7VkckQ77_ncfA5)N%MU#d(9*)FHBwzP2Lx!iS4+t#_d+@HEy=t<1ZPjYNPGG%0B$I%erhw!d9bI%3*)qg;sr6w^?!ikK6i&)#;*n>FZ=K zPu;fcn``$n`|yxo%JIc1F|N0s?a7EMboIMh(wVwqu2ko)^(pr*2HO6ekyqJj94Gd~ zJ!D_jliFVeKfej>+L3!%ZtlHr%Y7NY?qKO26~-^xSUYeCtKeUBg~} z^$l2mr#kYL?ypCudwN2;UZyXdWnMXbV!YJyLvuQRTsZQ4i?7Ansza;mlm19!*R>a?5%n+O^u=7LS9YORYWgcC0mEv z@#5m@uQ%>)yL0;P_R7*ZkMkdDEz-HVY}Xg}y-m+CALW*l{ul*L=w_G|q z^5f$E@VTO56S4^Iln_b>l|i=`|gv zUS&vc*3g>1Gf?>2arN0}`tDjO&uU}0IQ_dd_TH5WqU zMsl8M@cUKs=IywD(P;HD&a0Aj6CN17YFl!{z~gC+&#tSB)u(puUfkFle(K5hOJ#Y?Bgcc*F3;#RP&yt{hdjNDll6BlGp-|U$bwW?h`cITX@ZdKwbNu{4xu8ljt z{<{6cBQ8~`w~gLqep+L@X^H#W zU-dUz|J?cH{IC4SJO5_iJ#3af=@nnc&nd% zS6+19-Cwyib^GcgiJ3c#N@rfbt;cyfW;^ec=hHt_Se4~o-n=B+c-fX<<9%uAJ?Fh+ zWt{xBT0Nb6;_a{a*UGQ|tyy*dcdxa<+`@3{)GzHSD{q|GF!4XbbpLgx?nh@QdF-)y zHm%m?T28XT3GJ}H+m3-+p?(wh`JJ;=oMf)AEf}+J|4u>gGyA?;)cA^P&sDPH&7XQR z_sxCP{mq9jZ;cI zHkK(3Z{~Y}ys;e8tcH`UbF$$_JycFO_o3+K-15_3R^eQ&#W+2)Z+`eIJ#W!PtR*1@ zZ*_B>)IAp6Kl^XnGt9DxSi8L0Y{$h@Pja@JT`!CNd3BRvsPKo?^rz} zYSq&nv#O-7D{tnu(8&#no2#_rlKSa&@4c2wh)1RT(3_VstFbthcUte?=D5zlh;QqY znW|#mTfMz=<<x{&iKQ|9<^ow^(+pZQn)b)Ft6- z#C~P0ObTE3F3fPnmHYQ^@=ohY>PnjNqEN>5*GBWz`{SjSrB$bu21IRJowY1`a;m3H z*Zh{N>fJB6)T5FTj|lnH7^eKwI;&-Pb4f_sXTLq2`~7a|-nsuz?(#35zUbn#Cl_sX zji>F*HI_ym7mCf3myD`&GYh z@2xW1w&z#IvRiAX{*_Fh9(|^$__E4Ae(x7=l0QYQ{O|^}4a&Dzw0w8$tX;D6l@8R* zZP=cceCTrI+qs`#Jbad<-mJGFMkn>oq&pv0t=wg6V>eM|U&+l)ruOVgZ{J(iszvMU zK6d5X&i8r+Qb+fc3jVwK?f5=h&TW0C^Nz18|1%$#SzM8q5)Lauf`L?yY+g3xD^~o`_ z^Y`67c~!Xd<$mW&y5S~okK{#OospQa|K8O*9qaohTX~#p6lp*(av@>=KY?~a^2(oA!pok-UV{EI%jLY%DQpY?Y!|5mv|%X zsrBzovzABBEMFZ`Ze4QGR#0Gzjs450q-k4Duix@#dv;5FWbeys(^y|`-}AgA^o!cb z%PVIeNUWY}dug5VtD<{h>3jb(l<#@t|D)vGe}?b=hpeog*9Y@nf19#w&&D5a>XEmC zOkYjsT5a=NZukAZ{dm%&{U6qzdp9k8(ww=g`3?JyUS1l%Soq@8 z`TF7E`5X#Iw_jiovu^F#CaIwp5$m&Jva5P~(4zI>h9`O_3O@+R`p>XsS+dUVUAg|* zvp+p&G%V`LHQbe)R$Xy?dHupyg{(`i^K7#yI+HaseP@vCjVqT8ZB{;V4b9qW^Dgi8 z<-LKyd2gpx{L#7lpFyb4V9S$vwfT(3El18x3AH-%)qna)yOnRlI2X>E6@D;K_|Y`e zu)Y0RThE5PcXfRi%xP%2E7on+%5_Sh9dJ)jkNJ8U_r&G%ibreC1WTP~S6iAf&*e{0 z*ZWCPDcjv6wHGg))#-$SR&K$M>bsc%IjS$_esK4HhPdSL*k? znhQy50ww0yh8_tIxojx)en!XYSmot@Q?*T}=cQM>8HtjZdCQF32=s+_)FSup0O?Dpz8@6z>7UOo3>Zkc(P=Jw{z zA|IB`sM*}Pzvro3dgxuVb@x`6xW~Nf=vQ%juj2M?)mCeZ)mP>^>`xc%EtW4|Q}d?x z^seJVm4?QDr!z#bf$35rc%+KdGeXppwVzFg^ z_{KZi*7Yp8tqwU&kzX??zXiKi|BQ5k=cI=6qJ)-@M4i zvN!YhmG>Q=UoM}!xz_K%nqOO&Z;#P>XdN``z0cfMZ?pPTEiF6E=$AK7Tz#=Oxa9uI z-H&pDyEdCGZ~4zq@Uvd!Y<`Ml@1D{fA6Cr$csSx=Naf>*Pu`(boy&gKO?xs`Vqa>; zw7ym1)|)0q?VakAv0G)M{mwV>UghOlN{bd{RP@-M(Jjt2UeR?qpa^`@g^ZfI!D@gA2mFL|bh z2D=)E1}||=yd##O`q;D)wuk zRKIu8z8hPP-L+2n=p0+Lt+jH+*f=5{?x6- zo|CVp9es1@N#^zkmv2hHns;)p@;{@MVXJ#x=e=|6qUuRcTgzi>cl2a;n`|sY#-?FcYPTG8`_uj9( zwIQFiv}A>@o+=buwBw<{kLS{@d#5C?K74)G?oyx1N98`%iECo(F5KyNHI`ak8!PfD zBI{Z8TXDY7h-a;#p=-j+ReJsUvcA6DRT2{He*D4egE2E^d`NHk&rqwsVyEV*8gtEp zkUNW3bZWXaT2|Kl6 zZ{Yd4QG28M+_p;Z4|Og2uBG+XQjKkQ)FVB&Ss#AvaJQ0KdH8YCoW<-Rp(5wDo+-Fy z?rQbr<(e~J{U#m{D?HwRL&|@*Nd1q~Yd*i2SCO!D&98rUPvf5L@HPChfPq2Gy&-<{ zocjf<{xfW8IhzviHSgqi%jH)01CGDd{1!56FVZ0@Ml}n5c2(D2x@5WV*QNgqZ~im9 zTI$H_`9*+r%>(AZmmB{)d%JwQR@-qtZ)WvH&toSaTJS8+<@;UfIs5)Iq;;f!l5vUN z9Xe~jcYBVdxwWKe#2(f?$F5|XElXOzJ8br!%YrYbHa>dnu-bG!-?E*_f`&KE?AA}3 zq;4rSwP2_4mgbpN%GLjJy}3+lx1sUw zn!Fd++jeDJmAbrM%&Hst^cd28)zb3nd$Q-{SN9Yj{w(2l>+h6#E3W_9-u$0|W!-;< zKEYG+QupWIDYpGAvG11cRPDS8ujV-}zIl`{J)vs<#<)|Prp9j8dO2t1dEtGs+g(3f zO6^PLZaEs>vRho^^UuV~mW~q>e>yj;XYF`--{;HaaF^rz*O|6mT=P9fK6=a3saM{1 zz3zK5C9f}HiQk@ech~ox{I))OVqn>;zPuNE1$|$2y-m(}E&XzpympdK@=oKKzPt8t zUt4ue&DPNHy0F}gxo=am_w~h@_}i{J`g*tL+Ryoc*=xhTtXvivJGJJ{+pp{Vjh6jA z)wRpbnmvr`&Fy)z!cS@)Pv!+iKGlo&)4KQSacRld&=2zrZQ~-Qg&(ZF)OG!cnf1f< z*+T2Ic16s2_hjM)`>A%KAy55lPsF5rb+tHE`ttgXBcCTG?fx@o|Hfa&KIc^!JqdLh_+-{MVW?|N!0D_3z20H~Dkyq~2-m?G9Y?pP?q=&TY$sDHWAR9aE>+U?|Su6ItgucFNbK3fwNlpEwb!F$beic_$ zkLhP${_4fQhy>Tg3F%dyi*~--8kfE$(L`Y9mfYmm+FDEBynH$9>CIK&b5^a|t#>xd z@>IvF&P|%-EeB(dg>KHWD4f@kVEixW>7Ut~#W!B{Z+&!GaBXPjagVz%XLdZ?Uw!u) zpTt*tSKSYBn<{j_Zrl09BW%}A>!n?mqN0K8-8c5<{nk5udfQ*~`zf}CmAQXJa>JJ# zkI}KTJ)N_D%67G5Vp+>}^p<6Hbk2C)tCm!2^;6KYKNnwDbFT5Z2zbX6gP|TLVPsQv z@Qe53*Kc>-zWqZ!^s+sUQp$C_{<=@NmEv~gqUX6Q@ljvi7015U(pvQURFdwU zRaXmRZ4xhT++iK@$2KDSo^IgZ^Rv6|J(N1$@@z|>o%I^AUthDzwuA;Pmb+g0GqlWo z`M!C1U;G{?Z0_D#W%1J1&vxdga@lFu4NZJQN>i3;l=X%D+ zSES@@oXIzr_n*(byT@GTtgdm7b^1th~4`Tx8$jZw)kj z8ND{@T;`gp*wEVTA?mjG=1vH(6<<9q?OmULt>l^a2XZ7XY0vpG?a9rZ zqQ{+%8y^gw@lY>u)nARfw=ddFU3`7cHfg&VAtzk}uG@V->F)Pz{koLSGf(|1lik&S zd!!p)PwEz4?mew2n$e=?(u{R>`H}N(+?CsF%hizm?hs>+Np;|tbM3ralMS9sxl$T3 zLCIG(|L1;_b)m&qW!|fn7wyiS8ouM)LqoC5?INyGKBYVLHHvD^y)%2JwK_P|)bH)w z--}D~djHt;{K_e3JGp(`yyHo`bG|E``4y3V^YpfVL5se=yZ=GFIPdD&&w_SsOmpI_ zSsw%!?XsD5hEq(i#bMJnZPhk$rB$oUw8D0p`~H>wdixjKv^B?Fs;1mJo>rQ& znfxLk_;28vB|EZY|7~kpB{i#Zb=;3;5uc{IiwRy2}msaIPf7mvly+;#4+kle3 zWc$X1EoOSe&2uUH6pmcc#m)JfdS#6f2f)8n*Rq#G}@;u@^7C ze`+*j8d#~&L`dlzW-qPJ%4>B%*-jy>2Y^4L%`C-aw0TIuT-9be8T?U$JD zR5AUJ)zOKDHCDZAXQ#TIl`7h6{pVlZ>V3z1Pn*O%=6<7=+;Q;ykJ7wXFI<0`&5YC8 zbJgtc>y^Um-ukaIUhfk2^UvLYjl0`c^?uwZ9mZ|Fx${F_g=y7{tVN67EM0B3bKi3D ze|zWGUyQC_x8$a!^|9x(?>*Y{vF0BCw&#DjODCpB=vfvnIlk%4w5z!*cOCn>tZ84_ zkz3OiEsnXi`djw&a;2j8yWZc)G1tGf-O%;aBDJbJ_3Tq0f1LK}NTP0Ntcg#y$&qt& z4cEPXZ!KExUi?Yl?B({@6XnaK_H!rA{Ot7l zQ?u3K{|v47!!s|J%`QDQ&)4+MtEW<%7tiv}DG^_@>S#vS)R=3xU&b7rJySIFY_3i8 z?%Lmf=1vS>cO>u8h8tcbt5)=@Kje?$bDs13d)b<(bvx%@np^VuYS{bur0$QpM{}=7 zUMgKe}FylW~=7DeDme;uz z@m*K9|5&lI>!Ywta;u$J)$?`brS5)PzZ|hrVBPU>e$TBOz001TF=c+6Fr*?aeVa`midi*^d9+zd|jZPz`gbk;Ck@Q{Uncy|A%%d@*SYhO*XbemYT z_G_T^qLr_gRk)n@@tbH<+WELZV-v61)z!(GnRyY%BHfL&FU@?{wAOmpXP38EMFp!C zEz-)ra%1Jr_M?}V_=L)37T%uWb6;V%>2=@Jrtbc&{_9M>dcT@3bSc;3-gnoYM;80u zJ&oG2V&#hMap5J;UGn2X>*w#DeP1w}zx~6aS(%?*G~*IyKbLbb^6u4~cN49=w^x~Vt=Lzt*n8}`Sr?h@pTkBweeax_xix*6`tkWH?k~PwJ-jOF*WP>UcFNw{^tX2Xv~3UL zjaO-9sm9(_>5IJ@{_LH<;fm|A;aam^&rOSWez@UUL0RVE8_P5o&A6mwr6jAwUpoC^ zbl0;?op^_xk3vF{E}hsE9O81XQuln(yZ5SA`^@!2U0!wXTBP@?EnoYr_2oa>`}8+| zD3*&Y;w%37{tw&4I%Ut!yP`)5?q3WkxZZN?fruDo`2`KFXGp1w&dZFZ%d z75Q^#w{`lF-K(@JwKq>Ld9m!W>uZTwCCgToL@A9o--rmu!#_G%GsTb|jl-j-Z#J*Hl zm!OcVCZVFiRz5cCZ*K409yFN8Bx-(y_SDBjK5i8Al<0}3ti7oSsm3Kfr@n_@1$@8vODpP99> zr{+aTKIy!(#h_U2&Pnq>k7w<++&fDvZ{AKbgP(o5?Kf7Pl@mGlUVZt47khf2{bw+r z6FTR#pzWbqXoKSjd zpH|A$sF#%!S1V7RoqM@G=jbH7xmuKzbvBN^$74$3?F#Rve35b?40GWmk>;cCUK4#%%VV{-+Hs?r(fc|>1NvVnQDWte=OctV5uy#;86KB zvH5>ocijE=sw(sC%jKtcFU{Q1FyGT`}JA^H6fGuW-@aD)op*Ph%!8o+2~zjOLNc&3nzBmzI3D`hNMvW0~ZO zhf>#dS)E>|KEb(Q+jTSU-bHIS9Srm}PRUm5{k`=~wP@6nyXLO0A@Q}mR%)w5U;Dgo zza*}E>-fWGcW;$0+HolN9h1YpEN(tI)Z1PHjfl^R)H)J3q&3?%aQ7llRr|wnce2U(8C5 z?0B_GxhC`ap_(~L0s9_E#ZR$5{?kmfJN9bQ+ZwZdxBUb5&ezg6&-k`}@~O`2wcDJ2 z?H1GfQF>YU>5R^-8!@*{4jiw3n15$+-E5PqA^C4Qdw(sib$k@<^u_D6?xus`+3VML z?tk_znmPHY``b-reZAHe6U}_T$K`Ijx%y6IlCh2+*W6hqGhg+;_sH4luk`s{(EaWm z9jj)g_m-PJeA4-0)u+teamM`IEqi^h^8DjDtaW7<*R5#RQlE0^&{$_NEsI4@!cK~4 z{b%^OZR^B!FL!BuUbWt6)%t0no%`#wch;Hv{do1cQvA=plSONmtuYhUS*7;W(B!({ zzLj%JJ6HQ(nmP0CabG26Uy;k5(<9Tb%dC3bcVG8Fy?wX8(xShXVsiJ??uLZ>_!^749C$c`69iit%%NOsyqxEpjqLlM)6~UF3qCKHs&%55NtiCF{OWpN$>IwbZ z%e1zJyfoftvrhfowb1VUp*^*$(pHxoxg0k8TlwoXUtPZbH2Q@7nYb>;K)pJ#uUB(Bd~wmF|eh8cG~ ztA1u!yQNwycyde`_GP@B*IRfcw`RRta_Y}4xqHX^9d5l^Rr+fN0?W8R9(HOHr!yOjF8zOd1zGE4S6 z=K-AvHdCeB4^PUPlxq9U>6lXTwpWbp?dzzg6K5}~T^(vYHLzInnbK;r%*}3Z*Qbtl#mlS4#>@5!uJ@ZXX>Wk6_nyd%HBr&2D{56`&&~aMU8J+J zI%C#9k3}!P%{g1Os`6X@;{<9{<(Z8am;I~VKI7EWnC#EhcYaUbY%Njo z>FR$5FU_xR7i&tJ_3nyI+iU)xAx~Hyk|=Ih&ci%?9o4-|$38f&+VO90mTm2Pjdg8J z&8kaNjkTT?nOUq#uKT$7&R(hO%JXKgdeeP+|59U1J*^(wm&=~K6s?ZAJ?Boh;GEPg zafdFOhG(Uf^cnjc`>VVx*<-HN)|j`Y7v?0#ZJZVDdv8}3*C|d}+g%Tt17CCP6_n{Z z8sfh3MDJa5*Sl*&LO#El@rGC8mC~%`o_#`(_Dp~3r#)%Ms`Zh}w3e;7v?{%9SHb;@ zhWpp|ybI2)f2ekO;jL1>pqZc7i)-oy27faOHk#Pic(&eXra* zry&p4eD~d(9(O$b$$b6yMe~j3s8_!U6gl&H%~h+{QrT86Ej~uv+mCiS8k`xJaNp^SnzC#*3%b#McSeJre;sx8`ZtxNyIAM(9l`W zeJg5Yn&;+p3-@l$Har|`>bf^+MW3PYlB2N`_s6YGs>s@8y zmuoZUnjiP^o4kB_$?IpqdMXxa+g_-)9gg;${o~!X$AKoMf&18kSD6ZmnI9Loo_qS@ zi|1Oa@=xF0cWnENr&}ImEK6FOR&(y^iFZ+VpN1)Y6^(dTa#_&O>TK*r<0Wo;)RaB$ zt{34*iiL&&4vH23a4im9NWzkAs`FEKXs?>z9lM7!JFwIKn$q;S();z-XK%YbvwE-k z+ST86vhO_ijrda++H%$`S={Z~xn21)9v^Qx9(?R-^ghQmA_2kA!c$kz>XdyjNk#DJ zP3>*>7fa24zp4CPWzwqk;!-hlOCIb!u_p1SZBf?2z|EqCy=kSFx1~nBGZ8i0^>Xdk zoUqf(SJ(OO{`+s=WcE;FpO<=z)83lDJ$lN*d!6emB&Q;njFTfv?SnS%QrY%+SNr6s zmr}cz+KS#<#Bs6A~Q;7U%! zB2Cb2z*no5IM~R!WUScQthL)(x1BCqmRhnU$2B|i!QPNfDz5Kcvh4GAub;7Fxp>`- zo6B}~yq%`>&T-1Uopna+e5?X}a;+^wp*tD=%@zT>EXi$jBd zP31{5nycNJS2K6_*7jAMFXm@6=}P0!PqZ+_B?SWmL(kWxD>g*Mp1mq>q*PU9{OsSP z!_tSQT|Bv7>iO(@-Q8^0h;|Kjg^cMwpQmA$B3e{wZTf2Wu;`4k*D^)k`;UeA|6#M1 zw0*=}0f-n9v>@wGxZ9ue%*cDPEJR6OFZ}0sy>tw{+$BlPeYRz8U>;7v! zqsFzmT=hcH9Q*2rnw^iW|LT-{Oo27Lu)Bq@sLsoIMX<7quxe~7nB~qDi9Nn1x!lEd zR!Kyn>))W|^$*_L=-+u)Vq_V|S2&|%`6BaE%*At_YtLD=YW1#F-&f6OckH-a!bslz zya+c?URV@f332phewC$~Q|NNF_}o!zwxjC?a5R6cjwLJ zgFB!6jCuHYcU(VLg?sp;gWPV2yKBhv2Ll78F(Vt@d#dZI*RNB*+MGo)&#ZcSyT1AI zz4<-8p{{~!_U7DNFBlV;XFAila@~T^l)evIYP;q&?#{a!ujTq>{oeNyr+;+r-eqAP zvsrU7>%;5&_9qMI&ZM|%Vql28Z1a@-{AMrnY@_J85Eqxbq16+)v;~b1wmsQ3H*Isi ze_q)2E6ZKotWRXUe_lR++27mKt`~+}+%@;p*@R8rKjw2iZ}wk*q4WJr(d&vUKXlLa zp1QS@H#M#r?{m!oVvFGyH5^^IANgpLCk$HV|!NTpXx5KYF z`<*vlT4gGJ&-eF)MK4$w%*5a`KwVtVO9$yYzCme_8eO+xPR_*ORVf zUpkq3vo_+_x6(_>J&S%%TmH24&)L4UQ+h&QOfVD--v4%b>F56pj)`GvPh)jgo%3wj zx-U7aW=-0vP3v}r`$q=re6n7(&wcgOdailG#v!kDmWsXDDsAF6@kzlHAM!nmtdoK! zi=f=5lY8H_>7{N)bA z%RX~bT+)Wfth`CzkKCOxe^u3&taAN1pLfR>FUyKe*l@Xfzwe5d-?Wch_6*d1KJ(S{ z6Hklh?0vN=aarkoy?$Rq7gxhW@~lFrt|6aCcKTpH^7*hY_w#vf$7M3lJhv=!ahhx> zxMr!ZX}F=U;hIx|S9p#VWj(tbly^%iH6%Us*W>*mEC0G_3s&zsH}T+}r)rk%H}A`zl&n=6y{WI$j-GKX zblq_+mG9ZF#=uFo8M%KxMBjcE*Eg|N@B7}zlJ$b-Vt2Q5+u|ED=`JFGz zu9kjio%`o7cYAMMOvmR#qNjJ$>mOB#m!y8z`Z(*E_B09Y z=(T>EZ~j;u_0E31Z}Fu2Nn3aB%e&SU|9iQfvQFR3e}8#wYj*7XC31XG{mSR{Q`jx; zFSAa#{X^tG!_TI8wZQB4_h&x-S=jko)y_&^~Ft%YTNze*rR&@4RjPr1_uWGxL*ER?mNbmY*&$f204R`W62fH24bNl^y&E za!=!@RN3OcKl@Mj_}^H5(f$g^wco81{&dy1$DbZke-r#-{ww=a%yX*e zZm4s$|CdlR(>>-tLtXNx$HLzDm0!sZ>dHR;XIQoP zd)B|rujZfl&ycmYe)7p*fuC5{Z?&88Lw@d_`d2Id-rB$B*YZ#Hw^qkL`BXQhrZM}i z|1vxF=lSx#R@s-We{Hw&Pye@7*MHi~Un;K<^*i`wf8(f?qX9dbd`2_LXi-3X8N2o@ zzwq_eZ`G+xHBpuJUT#HPR%*3|AF3{%yQcl+?d9KF>!X!6eN;_aQ5;>-o9OyJ;^p;! z-&barEA8~}eEUc84!hM1>&=2)z1EuZUrlLFy0XStG2fK;-Q2GI*(SZo` zXR7;H?`c@@%wyZ~T`JX*Y~M_h>d=b36?|T1?cZRTKK~cGYBQIY9JbASE1sNn=g9HN z)e{!uYfIvt(qt05dQZ#bz4xRjj@6Q7dt6ps4|G`lWc{ym@BEhD?U6lw`-gMvSxfJh z)1`~lv-YhB{_FbaKSOA7)U1xT;g&jOFSq-%Eh~C|`bBv8%;*T|uph@ZJ^%49GxND=a^ux)IZ_S*Q%rWuS`{GTyzm7aFHmzrxR=z=^#_iY3dpC;Cg+9CK z-`uv>&_7(l=SRFy*sJTV&QpU_m3NtDMXh?a>Q|`EF?P|3SN*m|&aLU&IN|g1q(7I_ zjg}|XU6=6rc6ur6qdx1_O)IC0d|LLlT71zA>1nUFhMg^3vg??zc(QDk{=-*YE9VLG zU1Y_)y%gr+NIZi|l{eX((~rIlzPEnshl`hjx5oTTE!V1%=Zwmn_1r5i{Qd32f#!PW zCUP0C-;)$$%+GbKz*0*iS8GqVb*Q9C*ZkbGo8H{7Z&dyPL zW%VmHTzy{E%2->U^V8hdy{o>nYSq49D<^eaZuVZaa#iW{1C$QU!hFZTz+e<(A5i3@ zwRLLPtn1ps*PXsTOP>5d*U&;cVpUmo+rf#Srpr&b*F7Wad#T@#FS>klvyL{~EY0zm zs=1CQOJ#z`imvCCSNBaZ<;jaJ=d}8`eC-#1HOthdr=mNSsi(auTvk;rd9g3g0m(uX zCZ1GZIaAF&;jf=2Yp&E(E}3L|&gDhto!Q@4YGuxPo;JNtSumeFFYoSLC8>Ll z#5+#^TX*l-qJ=9b&6ZnUJ=@>sYV1J-(R9ii5wHD$%lqR<$Xl#RA;-2z>{6w6&#$(=ZrzwyjlE9E)Y z4f(xteLlwgwfw455r1Si*P2_4HO^&CYuxhH;QE~!uFkBQ)>@aqODe_gSJ%01{CuM0 z`YL_viE{00qLx`KuSsw26E)OUHM7rp_?cziwCVFVPkNr(_B3eE`Qq#I7OmXms~)a* z=X~tsv}I>qTvSDZW%+|&ML(SUeOG4tXQ%tW6xN-!b-%Lxu=@Lw%lyw;_kVG?xqQX1 zX+Ki_zMGZ!=fd`X9nvOW<%90?%-?(2$9|UBe};?9Y4aAN7?E@NO09Q|QQdc6gMS&( z{~26(XI1sD%y09*_i~y1S?>B5jhp=gzv_NC`TMTP;hzih{|cC8zOq}oPtv}6*^GJ< z-Tw@W*hXD38WyAJVlTea+M)$>uSH-}_%_vmST<9idgG30Vc!Hg(dg=<_j=jKkUK73pBn%W|zRlkj@s(zkz zHa@M+ZF}i{_nTVD6Z4{PpU>#g(<<33fAq&vXPy@iP^w)72Y35M-pJ#`jF&1!rfYq# zhVK;6G+y>I`*Qa@h38kaKYv)0xAM(~+bUtFR_reOtnM1$H7~_@hm3yF^1O9MPi0n~ zPnR#>yXJhO|GLmUCrmzYGas7H8nymt>#0@C#iO$2v@=(9anExuOuojp_gvCGt};{e z6)ST5BCTz2UGa0NnfPtuXQ5@CYj^s|?(n^sC$XkG?{ZK1)?MDW%~mV!+@`tiYT$`) zJCkaiBhE!k-rOmqX1eB{NzvB0b=BRoGP)|%45%=f2niw4%Uyb`U!uFX-0$R@$U8?i zTYD;=%r&`awye^crOWKvqV0d)seN)AJkPWq0~}&*{EemO1yu45c^G z>rAJux{)U$yi4;zk!@ercP#->DloZvDDFp$?;Y7&`8VfkEi3JezN>a6E9cvxqch*8 z9-AsSRdChUqJJxeFJxNwu@#h?S8P7`m0#I1Fn>yf#;v(}mv^oBzIn6M9ULR{xD$cs z_a58ivbhPT`=Xw>>`LJ&3EaEx)50XJym$W@>_hZ7m|3r_4n8EcT>wWQ!u9qG#H&ne-diUkm!c|*Br&z`2&8<|*p3C!7 z=Hu@4;6q<-o>`VXF-7Actu3JMAe=w&X`>{UD^3W+*uko@e{2Mu|BkLc|Xt4>(D zH$p!^*ECnuOJAkuuBfP}sLb)LC$5{m4JnqnwsTcqmKJBnj7w3LkNum@>~HzAcdNl#v|WR|uy?ucv7#3NDHT`p@~6G*Axi;r%7 z67oGOe3hx}N#mMxN#CdKIAdzDI@o3NojK>;r&#nK zb*{YeC&iG8LNBjHXlsZdHg20bEDPeupj08rr3L&xYxiuT)DDu*1W8lwtZq& z?)rPK+^t{XlD*SwRaa8hytcJJdap#9%7^4#_sEj_#i16VDe?5K)UhuwKg?DWx?Z$Z z@Y18me|E(@x#dbV5_dTc>`V+}ZaOEjt|sEu>W!7_M8oDp9cRw7WIe|r^L>+OmFbiV zd!sgPTa^0j$y+Ps31V zw~f77O3HztSZ8{9iyAQvGlQ}FRo$G&QoEP$WSjXcWbfYj$_u77EuT7Zm&eY@CuZqg zT$i2N`{Jf@)z-?mW0t}t4nEmZ-ar36@^<)CC$+We^HNJw|Mm+0^uYX`o2Gdv?F{s~ zm$AHey?@W~#i4I=Hl8|C{nK*FmQ{0SUX)-7({DR-@oPw^#MDwJrAx~*z53!-b?pu0 zjkY=T=BoJ1KAYJ4v-bSTk6N#@H@th_e0vGM*|R=x-0!6Yb1Mpst0Tof-BNh@9FEf+ zXRiK`n|Lqp!%k^kXR+Sy7m-p=UjFFr(cSmDOtJ0!$GJ}%);%eC_hoCU@?GV-%F|1W zR;~K|DrcRt@tRkExa=3masH_NHM^pEL&A2hy3D@q4Ji!yd5pw6Dq8PZyue#7%S`91SI51)HTfRj z^FzW{2j}g6BQk4|WSIVhi%)mlU9GX*)ud#5l|^T80A43JrLvIRj9bXfZd2?gd$&Yu9w_m=my`bp0>9K{U9|Z0d zHTSr6)!p~o$uoV~fq|l;FBWpHU&$C&Sh#*`sQ-$7^HlAar(uVAw@she`NA*bYN(xP zC`ayT+so%Hi@9TcOlD0?U$!^kP4A?2U1kTv?(L0RdsnI6f2&LSA)&74OA8BicFUg2 zF}bOyThg`fe#q=i5x*}6?_HPmHGA^8t1cn@9&_UkNU(LhTD-NJ)92tdl`X5Tp7wt5 zGWhndbMe}(rX zyY!}D`TcX8Gy7Vy`#<`x+!?aDt5+3f7U%=pgx$~i)oxt;}YmModu(|WGG?&O_U-A2oPn_sK`%XK!&Dr|1v zwOOy1%8HirmPcu2b#*+Nwe)&iN@HJ<^1PKdmc6(V8oS-^L9TyLAtwVvOL3gC?vz=Z z`(BF4{8+86wQj@Zd)dP2?mqYOwdT%C_t<+b?wPvZy89D)uI~5nJ*Z-R#BJYx=LegN zbdry~-?ubM=|_I^H`y-N>({JjhHv$EyR%Sg)y|M3~M~$+o@446QO4(wt$!*`= zz?hAjT#G{mL+`Bnx@!0L%I0%gGvd~2oPT^()_%+L!f4ri@7byMW?uR>clP3`A1y7n z`Ccy5TJ&bFs1^Gfzm2PPxf&Q4{c1z}yptDbB*{iNmEIUyf9d#lBxW-Xi=^5Lr6K9`Hz+}7_|xpTo;Yh#o1&abP(mEMZn zS>AHQf9V;=Hz=hUjDw?8V}G>nc!HYN+@HLZ1XEoHyG`tr75 z4U^Q06}MLIDc8Q7RAiSs{kjoL?5y2Y-?bL4%Ik2eQQx~Dq;$KSOyTR=ZO?w^#`-R+ zNLZ7S@jck%`H9|%QhR0AhPvLY{>F9r(Z;vg+wQLUaca@M}oo2O>~ zekHoT`LR+~*vH7ugF$nbl>G_Yv~K3QR})YD3^TX1pQrDg?x_Vc3*X32) z7V3*`sX4Ltqc6AAmp=}xd(TYGGs(0_)x zjzX1YuiEvC)NdX68n1rl>ZdBDjUt~`%{4!?vqiFQ>dUGB8N!Nw#rPdM=U2b(>Q&3# zo2IdZJezoL?sgZK&#M-d+%H(;ws~>t4n4ty%X{M5g^sPPmeNwo-Szgo_Vw!?UqA0W zeKq`JPDh8_N2$E(2M#g9wXM?MqgMZ#A-K4ymQ(vam;9$|t7^ZV|IZ-P$Ym94r@88r z7O&SOsWX0;MbCZYEj@arr0CSdO_}y*%d&Qc3JTv3bqGBs>1I5;&^CI)?UhEklACX@ zH4Zs;>eh~TA3Rp3+v&^&gch_-NePzEPM=s69G1$id7)>>Vj-K2XrFmY$h z!t#Gh=AIVGn*VuUYEjj>C!h9aa;0xBjd7arVNmI=LS` zr%T>lbPr{1R8Kv1XmjvsX|el3N2XakD(S0UmZDeu@^@V7^HS@pW#z^JTh`5;d69F` z>QGUegA#Mi*R@THpR9QB?NyJrcX@mpPru(P$jfiA`A(r|(67@IfA*wR+RmP9DCqNZ z;dG^GK{p?MaaDljTqGLT+9Y}YvcCd+s-Xd0Jyi=z{mP0Hm$+BzZvU)R{p!2qnoTyG z(~X{UA=ySa|wO zxpnJj%B*_o>b5z>cWO*bWbdtQVsZ-@40FR4ui5dkEHQukrL&=-p$R*jSp+u5t8B8G z`{J7E%B$x>LtI>5ty<{$;AQ62X)8_gC6BM$7BcgM5L6#~xw?pBurg72qzgr2%?Jyj{YETnj9xr^Vb)i1en zUP{m3WB2g8t=YvFpSVuttXlWxck=UTN5hT3cYd@AH`A(+>%3|Cdr|K5P={bC=e;Yp z^=u9)*(`HvO~qbUHuv(692A8PEn_MR;kUbVB#HLLi`t6rP5nVqq`JQq*9^(J1tx$?JE_ttow>rv0l*L-?= zamDKEo6}zP`pqlP>3Yf^mF(ehFo0|B5|KLrnQRI(TDQAutP@xoGBtL~_pY?vrZ=Q&=ei=L&)So7{~Xk~ zzO=32Yh&sT-}3=lM%7Li-K$Qdu3Z>9%hEbS^n6J9gr`s6`}N(deX!KfaP`6?ewr)I zV-EX$=CwLQa^ zUW9I%))m!pGuS#`U}sWiOvm>{D!bR6-1YW7bJVWM4W3~;7Cve<+_LI&t@(P_-K%uW zy_e{{beVga$7}xQd+FA1ttX$po2(cbE3nkBc;3_MJL`(4mxjh(4Y8T|uc3C>hPITcvmP^rq!Av(rQP zvn*5}fNBBL_*ds%9=iPGR_W?3Q>J?c?Q2>svwr^OXN#7`5!d@sH@m&-LcztT$q!3Iy!Ixn4*kP5|35==;Q5T3M_gkIeyPuz<6d$q zd#nB8@NGhm^k*E}RsGg#p`@4mQf!@kG3@K}#TVtVbMJb$(6eys$y??RxitLc^~1Az zx31bNott*Q*XLRE=i*yW_iUg4r_{bQqNm$CPyb5Mw@oSk8R|>-tKUBK=zQ1Sh1mDA zAr*4?nDTmk<~!F{OFcI%&svqYHg?-xTUpWI4~@|4O&Ra8H|-GPS+#0MY-eco^`jU5 zGqgxKeqnC<-M!&I!|sLu88}SZb@jyC1FpD-yLR4O`MmYSkEdNtYPNlTPhH*Kb@3{` zX}rAi=34O;v;8Z!23|jMyx4TAfmztrT8%!}n+~7r8VceI%jCb8*gs^O@GW!Y$`>ym zFIm!Lq0qYG#G~F7*9=$8R#H-Tarwe{3gnGI|1jfQ`+NWXyYOdjA;0z=#pMdyBjO~q z#1H0$-2S`EI{!endj6w)?a$yl_8=g*=y zt?a^6RvGK@rM`{+&{7qm-Jg0tWbJ>3;^cpuEvNmt@@;3i?!LMHcf*2}E=zZRmD#&y zrv3f9`6s`9*s=N1`iv@SEdz$Sm(kTW;(XRW^Y6c%Kb0P_SzpjLr|GU;!NE7z~OM6+wst}m**&n;RncYH~bUgpu{IN#<~Ys+ejR_%#?QuG9U-SeNJRqygy_5Mm*x78chd2b1}o^$eE;CnuocgmX1 zUluJuHVMqezHpoQ#HsuXkGDm2H{ZE;b!|xNs(m|OU$iSe_4dSepHrV1TUJZf%`QB= zd}Cae>ZHnen|0xK?%H=&KM#$)>f^TSeTm01&5mUj&W(4E2Ch8tKE?O1$}TzKqftMm z{b%rbS>L$f)+YTK^R6>IkA9tH_eM$B;_2I?zxv{?`OJU+Zu^tp6)}+?!?)Oi+FoEM zQi)hJZ%z8vdg^SG5iPF#%%lsbLw zZ1;?=N8N>MPVt`E^lQzk{aZpJRw~6V?7sImc(3-=I^HwBuP>L?^nTVm{o+9M<=)_9 zbv(Jx=NJh+u6olNS8Da{o68SR8}(yh`&9Q|wu*m{W45RJnflfPl~;e=>fg=Ladu~Q zVcp{CTPLgk{=N36-92%!n$WlWR9xWzjt7n9t0mvYAN%*8A*0T3NBh)w0+%O5FJC87 zJL~C3-(tIm-dl>jKTn^$NBeSe%*vZrYfQTTqSrm^5rt;|`=y^y6b1F`G5Z&{i$}3p3aZex8yMM6gZ&BCa_f-{xJr>ZBIX%^{n>L^o(cIpX;k< zgzvbXdNZRe`c8|u$xi8Knwp2-bYUh`^z6;vIXQLPogk~qyuc7!t2ARBfYR$cW4|2^d1!aM?&%3r2YX6%V^q+w_acfGvq3t@>bL+2dsXgK1 zmV5g40M%}`1&7~U*YC=n`JF3v@vL~cPo?c=vNye%c6ZsYD+)W?m1~ZwzU1GeiJ29JYZ`;xnt8OH@ zhWMqr?FFC9?PAZB&D*zc`)pO=8j}4wR4l3^ z^u)?dizk_F4J>yJ3DI10o^i&D<1N3fRzA(;o_P9!>G`5covfYV z6XRB_IO@K)KO}7N#M`DP=5>ng0X2s)!;35tk>IO6-_}2_uis*?9^-uKJ4faT-OJZ` z?9LbcShjP1!O~lnOFzC>kEjNf{TgL6Uwi0f7MY(3ikt9T35zSxrui73J{8>LllS8K z_L2{l)AMU3R<%#Pajo-B?1Qvq;k@Lx?ptGfj3H$opLw~kW0f7{xf7>kJ%N^xb0+5 z&=b)Wv$bd53f|`0yK?`Ouf;p1=9O!y&sn#7t7tUem36i|HY{Fog;#Osqg(N9X61)- zKDO}Ai*7PKbzR?Pg3qQR%T-saBZWmn0|ifA&-G1nOI>W^ooy$+wq%dV$H<(mKR&%= zvO7E}I;yAb(Y|nf>!{wWC3E`ScCP6SJnOb|_U;mw_m|vCkEV;5R>@715ob6qIq8*I zvX*1&*8O#-Op1RBWd^S5TCqMZY^%)Ot1_#eEe=_8u1Rr!;IHKsyj|-*ZMYgTrC#7t!^B;F8S8`D|^jv1_o*iuUPR*`{;V{TUSd@ncVh&V(hQ~~W zTGymhvaXxA^`hyEFKyw5)*jpaz9#>ivF~??+sAm5{P?fI5fuln=*V3zNQug9D!cN@ z_~=LNC#!p-{U+|pkLmb1Ggv#+`nK3zfhB&MbQ{6N4pCW=@p^RcUad>_wqAJ|vNwqD z>fS|P?q7SqzdXR2q4d@37aCI6uU|XpbA8>}$R*i=;rH%3Ku540ew{!1Z*Hcm&o$|s z{F9$JLZfwMWlo&Xywf#%TjsZr>}{c+qOPyfE-TmjJ$c!kEBAKVp3pgNyG;Miyr)?e zp@wVD-(6m|@#)Nt`)jh+%#@eib+;mX)7eMXJT<`5vswGrs(DkgYS_0# zEwk7a$3I(VGXIi$w|Ck_ZRvm5F+XP4G(C&2pDsMNdRVgg-o@ZW;kP4>_%|Q?t!TD# z`Zo31Ic@(L&R!1PzdPhV!__StZn++OyUV&lkVT-FP!sxSlz@Y>~yWS>bZIzvFWMGrWyVvz=tP$?Z#V zOvl&vXEK6y>!zk1jGS9+dA*S6f#>Qf@$-8iMZx_%+k zjCM#<15q`XS1Z*df!j`A{~4Br%tvlKt;N@P((qe*CE($|RerPL^-Xh|roB(^(wO(I z^VWIRTp6GHmtIy1|6QVNXRz-`tJ~LryerrKN!qZbZ&lg(M8xEyY51zu(K@R))d;SX z+QAdt{PX_ zVOL!GruF_gTW*r~S>HHnf5VFF=XS=FmMy-j?pZSLZ2G=bG4B-K&4KrxJz0|Dog41` zpTSaU-Q6wcT->Xk-cP;h;V)=jD_ylbe#gJbb)oA2D%O3x`GDE!>vX5wnP(S=>RbuC z6@4^UwRW3U)QeY=w~9{P{AGChR7WS1+TY*Rhi1%>a9>%{xo@L&%H1Nal>46=y(XNl z4QbnY$0T^wR?+Cm0jsJ{-}IaRy=#ZooVPc8*9Yw=+@27=ZR?dvHhEu1?K$`I{4Pyg zF-fUz<+_j}{h3i+;XQ%}gDuRjz1l1P@Nl@nn*E#mzAr8ny>!@9|Juds(20`#M&DOm zGYb1vx9h%_NarP%>94XgTV|9s+uQ9vdba8c*H694A)dR={EE1m+_*mU>)qL;{-(S?)}{H&`$y5= z`QyLWhX3e#ZR^Z-wR_pVtf!mWu2x@?`epGVJ*v%W+Wo*Y&$TPd;!@-0diiZq{i|?& z&2+=FuT8$&9EnWceb{fS;MPuMQ8Txl5#Q`fUBh>Rre{INt z+=-8VeLd`!F6Zxdby?KuT@S9p26H|aQ4)snR;IK=JQNtQV)R_-!$GYs9m>_ykSqF|}6-o<;o zUd`u{SQDjw#H}N(oTG5MRoB_N4S_#mOiS+V()fAr*|9HKdS{E}x_X`@FV3FvG~&q1 zU2eh4#pRTL%)h=`ZDs76n|{+@rm5`ta;@{#v!(OCYw25uMeLb(bzQmA?wPBl3|Adn zJvHXh#x*D27IrWAwUljXvQ22gUq{)x*ep9Rnx9M+OZ@4DyMPV=Jbf$`EdtTJ}=KbwK?}c!=2y1?-g!Q*OPu1 z^}Xuy^|*?*t+#Y5SxalaicAp^{IqXc=euWJmfwnJpSydfqhr-dDfe@$)h#2gFLAJ2 z_D+1p!~YDv^^5NKeNOrC`>oBEeTECB&EB3=_IsDz#g*sg2CvGS8?aQ%S@Wx>Va#H~ zhxfSMlzZ>$PklKt@brUiw=A{No_IXFduQUJ-6?m?W4FC(TF13UZN}x0z?bXP-6Q=~ zgbyY!=!;mAwDsfOEpJ@2Csr*2fl12E(RT`7TnqI+8f#|R9CF&%b)Dda%U2zx7SB|9 zv$9k5dHzhP9}DNk{@eWP?U@!e^WIsHBmQm{KAu~#b((0`{j^0*O_NN2dn6?pcK(pM zqGn5UgNk2S^R(d~t+RTLlkWtz-RGWh{LIF^FS?%IT)9~=eAW5|J}(sJ>E)Z$SXBu``7NN zmezOom)-sARpGq-v7EVmR^p$q`yWL=G#|6ctGjae$J+gmry z)v8}+oWVZV4Sj7=SJ?bE*zt-jv+Atgs(pgxYbGs?SQoC66Z%QnHEz$EEYp?mO}S4c zr9fo%<9AnXcd2^2X?c;rtYunVQOB%p_O_niurt);$i=c*&(iBQZWcCN_Sed4 z?%aTjv(qG(?+Tw9p|!5`yUxF^MO9tlTi3gJ9c`635p`_w^wO!psdq!mZUl#Iw)$f8 zDK8-AjNy+zrazX=GWhD^^5L#^)Y+(Wv$xgs)rMZI$#yHK_LWPlT|GN!-HTg0bGG#N zA6#qFy=kkK?53TUcADQ@8@BA#-WONGSFX#Ndq?1;rE|wqHnXP{UP=a2^17bKu6p&K zA)liw`LwX@WGfeE&+t&kP~K8emnmOGKAAX&%%vaSC$F)6k-h276l5W>x?1KBfb>++R9UoV{iCP{v%~|ij)!w}#FCTn4 z#j<+UighceZCd3zDRbWX>gnfJ-%CF%!FV39y67mF@6uq_sM@A$Gy|85cm z53(M)_W5YE#nk@{J}d2EW7SO0!(T_)JyB9NBQ#dcWOLm&ZEJ=b&(6EgW%kCsoU}>H zblH|eH`QIMECX*_p7jfP`fB&j3ro&5ZU68$;|iZ@MgQckvyO*W7xu)uZ_2cgk^FjS)8(d=^5~M+ys@&&HZ9@| z6bzkYByPFR_g6`XtESTO7k%5a=J~Ix_}lh(M#$9*q36Dx`RA6A>i4IL%yYkYXL-TI+MeWQHXAA!H{ad%+m%Mxb zev2;eF`B6w5;*hWs-2IHo-%!Nt2e4kUp~s3cl-5yo!`|0u2@YnUy{GVW6s8{{~1<> z=WfyudHKFKIaaQ?>P}2|a7eD-U(@@`lMcR|zh!g8zt=bS?>SWIf6m$I?0<%Hk>z)G z{@JQyr#?O78254+`zKNJO*Y=0o4sP;hI20_e)+;TRmn43_1ULD<*-9nLPKwF4KG@E z>eUDS$T2Q)%+RXwi}wfEB)u4W+8K0duy@& z^0cmG-}##jkE!2Y@3-sA;-=iz&Hn@+t!i(tv7FlR?qcGqqWoX&p=Z|oXLuMkZM#io z&FRy-R;-?4F>`U-mS1I>*RQXNSGeN9z`(ffZ^5N?YVYoNT)(%btfy^LWS*yL+k3a0 zLX##uV3~ z3oX90&%5S~zmvqO*RvYmV@=lu6%6IzINHPzRfJV{8`?V1Rd3WSgevf zIo*Iy*EL&jxt&7>!Liu!!{5W3dX zSJx-&UgV;xlF(0*yTfy@_P*Y39P9OR=G?^2^FOWY4UXPC^Xd29(}LL%oilDN%baIu z_FOg*+{v-R`-c)zzcwt?5Ue4Q@u zwK$~trZ~{M=|afG(*ZvxzFid=raR}_B6YpHlfHz;ecBRia^}qXqL{Wls|}7l&RVC{ z{nh12=3(D;wl%S5nxkKA?iJbka$SDEcWm7;Rks`ZAJ6pGnA8epF3Vc>K3_{qY0AZ)%pNbGku`YwabxAMeaZx!vk z?6Xz$x>UzY&4sQmk*X6-e@zWzk767t-m{0bD3IN)gtA{+}mu& z6b@}TEvM*fbXs=SeO4}ym77l9+$gm!zBW{}o-^He&8wg4M#n;DJl1>ks#k5-vb9!Q z=azi7J=Cqhz`zi+H{|hgy@OxNUU%I5ush@%x9GtPtx0P)n{8T{RIalw`QnRXN~`ph zR?A*ajs1SpX4$jJi!W(h6ZhFP0ki?_Q}@a*1YN^CAMp_{L@t{?%M5H`E}c`I(hRi)5;42rB~!w$h1AZ z&o8*kYFA)r?Xg`igI?|WHT}HyzMH4-KEG+p5fK`4)#`N4*0j4_WxB^Eyq*YK_dtdE z-zn&BpG;xWcP zUmjat*)^MMRm`f5-RUit4sKdyecJWziLR@Dd2z0vEj`|HO1IxrEqUC@En0e;t+rM- zc2U^jqOErlJyxxhd6(x}WO^=Z>z%%vQBS|F>fW?yVZ>ZF^EEYz4|WC5JGj(rjko5x zV#@_yojO0R^0%D!JDO#=cX>$c)zxP=2cGJ^8M{dT8vus9>nM-%_)d>kDtaZ1~9Dvf1+T zuT$-pwxupzy>wOg<`s)qxNSWx((4*>Ki#xEYv-!&iy>?L7HOZ^@#>Jy&b!b3H|Uz3 z`zrRZPiyJDoV1${4T3D!g`K!=U6&fRDrfcS)B9bDR%!Y!RL#&Am{oZsOw%r~F(mZZ zE6G1%5A)Y`Dy?2LJ@?`!Q_;Y{lUHS~hOIj_``vY=F!ezBSN_sF*d$k!Rt2^tM%FD( z4SQ1Felh9We+DkE@a=hNwzuD{%X@kD#C7HHlSPXs21@mGJ6LSIT&M6(Jg4ZWYvqQN zYr1!1llNZU#o_MT__^%+vpJyCdec^1%<@sZVA5j{Uq_u&Ph(a_EuCWgb6v#;v<~ zd-3F%Yh$OcJAGk^-=Y$%`;r8g3r6gHt#>JU?{fJ?wW-;$tj?N`eeY`-x$Qi0<@4^8 zP?z_y+x(Vl^(Ht+lnY8;lkST8sCuz`s#ny?NRLf>KO4rMn4P!wO?ZBDkzrW#rC-ZG zoxj7j{AbX=hTKZ#SMyKX7f8*2>ivsx>l0zMDXBrP~#A$`zTMq_DK>mrOWT9Z51^C!|Hy6fq{WB{>S=<7jGR+?p8gzsCQ@V zJk=E6qEAap?Owe*7+R$~F*LMmuX(8Ji``3JtWS&nuJ|+hr+r<)jg|iyn0DU(ck_4b z+`4(azh16iux@ME`P`is&w7V@D=(VIIb+Qv^Q~oJJ6B&76+g4|ijUuqTK=rJ;xD?l zEmNC$Us(E4;&sl3y2YWVdb6}vtvk{AoaNl?re?qv!nK&cpB<^)iU>8_|{!A_S64t+TORUXbo%B#r5qWB`<#&KIyz~ zeA!Sc?|O7WrKJU^L6{JJmi-dXbd&ag^*dyDZTio^J>$#jdb{(*fr9HMnUtq1PZSgs zeH-dK`9iB%QhI3VT$A*rAq)&mT~!S$=mW_ zmEN6IfA8MkzSeL51*g?p0*{586;$-NpS@`O_v?n;$2MGW-c#nqsU_33Vw3L^^AAd; z@h^0E=e(K~ua5}ep>z$8FH$E&7 zGbmiTs{HGhJ>+%Wf)={TCQzQ1hMJGXMbMQ%Hv{Zah*pCN5)!qu6zDdyU>={=gc=Zm7>dHg$j zwpe0)_H5-)+1-;&%fn2=cjT=|sfj9NpY+1Fo&WM%(?zNGZO^T`oxaKL!Lz*^wR8$~ zK7Dm5>H4|I?fvG^&~tIUVXM*|#a_9UAFJmwF_R6xD(k&5%TCoa#{@KidY)Hiinsne z$>iR?BDGV!)!Y11^WKCMtujCGtzyk1_wa(;*o$xKUG>XuzWw<+>B*9{%X%|c-?#aA zoM~TZ(rIb&t$DAGy57DoZ0js=-8S^Gb<65$rYm-STlV%`E8)3)IU$l>8pk3u9_=qY+$|X<&?sgi*^W?KKs3D#_SaH>Iyq znL0IKNpJ6~xs?%DCce8FT+$mTDjKln+*=p@svT7ecJxhJFEBkf;@I+BS*iH!4<~aB z+)ij%R=c}xR>@Sij@TD$IWah0$>XWZz1d%`r2e{nZ0c#rhtd+)_u6f`TyfcCMcYlk zTg%HPMrVaiUh^(&+1*teuUy)=Vz<(&t*5#=)`pyW>ps7H@#FV?ZzH}fxKZkLa*e^& zvs`yR?uwR`m8qS4bJs4e85h@0?7S-Ydu#dAICrI0ypM&x`seQIk6-ewXZwqdQzA;X zn*CU1Sh@D%4`)5TT<71VE5DxH9J(grVr6;Oiq#Dh9_)TSt+&9x>g@We*`8JV18-=o z6O)>e^TBD_t!KaD8 zmLH8<|IxTN;Jkb5of)rXH-#U+x$U98snx!;<@1*s*_OZRwo}ItI3#XxGvnz;&GPY+D=j7-0$I~v8P-z6y%uKghn3Ql=YuM=-cU#X>7j?U+Ju9jqY_$#hQ|ht1rHmwb#k5 zme{k#ch2*Y-g!;CxUT1{S#te-NL#nr<&K$l_DiRw2gK{0`6e}MYHCQvjXRy2=d54( z=5ncD+T~MgL$lPrJXSjSN{)Zg3^m`_qjrh?UfbU!UHf*jH07$W9aq@KGet&o4 zcD~%dZ?D4AJp|wL#n=QU|4|IcBe26-zZ=%Pn7-n!UKa{r1tXO77uzi>j8cT2UUU>gLR3DD&>C%iUA+e&5+w=+F50 z!)a^7_aXdo1+P$e+JHoUe=0-7c;+Z?z44$B+R}fry_J& zqfX$Rh@;tgQ|Hdn2{&JM&M#ul*SIfPhj&%FD%ZR+KBxD2O}F*_n--3jc0E5l@ww02 zBC8*>)_dD_n!ktJyRbD?B_$9}- zwDx6J?QCz(C41Cg`c1y-T(xZBte@4bffw)SWnEvx_wt&?MZunsuy=ho<7Tfa&Z)c_ zbM@%?&G%x`cD^-ETeax!&c9OS*|%-dPfIVVnrz872jp`wX2rJhocwhsh3B%P))g&M zQhT-fPDgsoCgs=*eMYh|siMU%bxI0D-K9S5$`q}P*;g04=iblMV#&=`fxNTXPyXDQ zr@cSq$a$CQdo@<)i(TXHo%MR}uF{uxzrXf`o7uV*`1nra@DT9s{P_&%cHbo z_CG7LkeL0S;hfj{e@mQi8|HA&M+%7)0dh69=k=&7W zV&{Hov`=|%`7Ye{)YCZ7-2c4n9UZ+F{%}DCIY9wVg|?ONn#bIVm*q6p&$2xtV)4zq z&EN0k$JtLxLJik?*?#Dwcc-ciDuYbX2q|rAF`*Izm>ka z=h3{!f&I5rnr=?rcjI07rS;h&>r3*oe(FuMF3Fyp;_h~D_bRUTXVFbnb3!V%iavV2 zX8P{8vgaZsdG~dz&)c^)%++`5m)?6GKLg9-%C-jQZhIDzb-ieobxh=qw>Qp<$Y?(< zn|5l7*{au}yjni1HmL;0XwEp7dr^G$)Rp^Q-V3TYwBgj=(EJVCWWPW5|9ff4T9+mE zA>p?=XZ>fm9Q7|?xpp`sTcLczlbPFB?q7PHr>OI5X4maC+jsp~oIU5u;#`)Ap2~rO ztEcajxV|$yZQ?xUrG1a(mT&W3x*^^CZoTJ!8_sJ(mM@o$p$!Oy3gb zaWq_K)%DG*w1cPCEL(NG#A$_k*Y?f5#I~_)N%f=ZxbFQS zF4NpLub&&pb@f11)%A%mK5f7Y7wM-wbxA922Z|KQBE@H}O|lZUdM9?@W2=7KttxA?$uHj3cU&=>G&gbk zt=#DvOQ%LkhrSQVvW$6pQ|7s4IcU?|6d#^>2UqW^o&1W=ap^ScPgkD&*iz_Qw&;WB z%m;5KO09{zc*f)YF57)uPv5_iWh}Hf=GMw1dV)v4t={gv`m5XZ){K3Y##(t-w_axp zypl3$<2`Ss?5naj-(7wB^2ws&-KQdMO_+Hl=C1zLIC(w6+ajqkXv)zGZAlJ;G+ zPg7P#aQk{~sa3m`N<;r>U-fi3u9dy^p zo0WNSwf0o0yO)-p=vvizR`~XQvlCl-FInbPnr`lh51Dj!_RA@6c0G>D?>p^YwEM`r ztjIYVLpO?vFL%$^%`&wQndtHC-IcpBDOEMeg#o|*oSmbaxh^~B=;}N^52H2f9*0c1 z@@-YQ@yriy-?j3UHr6c7dN+Hq^i!?X!nRDI5C1b5Jd}3-Xl*wovv2LgtHS;^h1xSq z8^4x&y%b&ZR{qrPUGBT5uIRioXRTjHlbS+ZmcO{!Ugbw@cb8Z#>~T##nqT6(V!>%G ztvg#jd91yeJaO)PZ9x^+?78PwTnV$ht-Rgq^^&gNN1x>{mzue5m%iiSv+vH=o?rPs zcT#qr&9f_JT6vdDle}%Lx4zBSSs(UUUs=et$W->4@&1PCKi0TqecPTX&vRk5+JY53 z^pYnZ=`X5(@yn&iRr;cpc4~Rf{mK(ZU)~EVKi702E?;a<)%n;7aj%mWZ;#eox@OJF zsbwh!Zda@CJ^id4)wTM)qT;&lb(=DeOYS=UP-^4KX*ter8&)lubU%5=otvJ)6GE2# z_Er`>@oZyo?e)ED%9YEC?>##ln_jWUqHpPodrPBN8>}@r_i^_A=IFUX7jJ1jnyQ)k zv)b&Z;QC2N?yi_?_RcBll~y{#tb>g&rpeBD5x427*&1urV@6slpY**CI~DP~_e^j1 z&3&$Jdr$nBvs>xi+G3fGmHVzNE4izD`N7%?S?firZXfaOlDyk{?c^ix*p!K@r9=8A znXg);9}?$P5w~JRSEqLQ%_@%jRz3Bd|5RU??haY@Fkj+|bsB%`g)5$H*;8V&ZhCE% zo%}Ox@$%;(&$6sfg!pZ;u6r@#xu@sca&FVbQk{pM>8EeZWb1!rr>L!%-+Uo@Mc#zd zfq`nLp6?A5pXDjLdCv3lRqvK8N^sm*(0;VfcGb;ks~^m9OfFl!E-_sBl~(%ZZR^S| z7q2?XWR)@h#Idas_rh|zCT_0%8NJnHa+T7<&zz6!;*3q%Zo01iwqujF%aqHh)5W8c zBHoLvG|~EzoSwSH>)D$*XR|}i-fK#|GRq_B`W5PqcW{2U-m`SN)atpLKKW`}OO%DU zJ}dXV{J?K7&pxHSU8?7c)|l5kTDdH_B!9jUlObOVsOm()lrOrMV@qvFxYe20cX8gs zYdKwAn${5ub_E;{I`QOY^tmHd`r6w<{ZejLt&n3AUJ#|Z?7P^#f?SFUc`UZs^2k}%^sUlezHeB|WEQjVRs^EP_kb`ED|9aa+_KOwjW*yq9l! zinQ!^TIsvoNrn(!T3DSDnhz(keU1q8l0-k}T12ZP}~U7p<>) z^*eA}tw?`(HoWvguHF6jdHr8fO&VV%N69a?NPQ;LEpVyE?qumQ_a&y=eyf_=d|W(h zZIPLqq0H>9q2j5px>lG7?&rUyI6Lvz)>XgGt`xdGyYyV%ny$Ovu3BeqW#>pQ@p>t` z_U^g-w5~ZTx2JU9d+OrC`C~n%|Me`JV4D-~T2jri+vq<-TV|YU z(#~VY-AIA)%Q$v`3|+&x8>6g82q-gb=LkJsj%(hale}>H>J=0 zbMY(B>6mkR*O&L&%bJhu%TQ}i`JOfVpXcHmvDcnQ{kybm)3&4DCZFwdch391G3Mz_ zLG#ehBA3{CcemFvZHdubcWtfk!Hm}#%TirUeeG;LWj35Tt~}$LrSjc%?n+u(rLHbg z4ktd_Ftc5Wj@H@vPBHyNM)Krsee<5X$~r4go(j!^#gk{6#ZIl7D(A+&sLFNeSEI0( z-)29)#55*Ju8O#;*^jGbcUi8>TBW6xvtm`NOYK+Vg6F@RGrxb%0;huaiNAc8?y8Qq zJI^%`As8Zwfyajy^A%^CTH}So5?DUj{x6;=t z>|A={zM*H8Gqx?zSp3aUxO>Me{&v%t71tK6TJ@%5Ucu*eCHHl{^IuM_nGkElrmnPm z=9JaXzi97$U$K94+x0_hS3Zk8H`#2%pQ?Xc(~li}b+q7W$J;Qk6=~6~EGAoZ+gB$} z&%I{*d*|emyEdmUR=w-ontH`oto(4(r}M&Y%7%BtHVaC9Tm7=`N;Dm|C%gQOI9~rL z>DcV;b(K!L|1-S%-F^;y-a%sCyd8<;ZP&mSh|jj)dj0OJ?&9sic9pTuvhp`ogh=xTgM6j^Sml@z$BFFV8L6R<3pQ^6SZO_WtQuxxIecv%Id^Gh<}? z-pBnDJi4%Gve{34?zKDrGYGA{^3&@1*|#&Yt1_3anEF*zXkTPX(cbsfQ_mGoEe{Qi z`x^RrHb1-k%vsALAN}MqCtNoF33c(kTIRz1agveCv);Ew<#|`{Xc>7t@qJt!(|NgU z-Gf?I^Z=>Z_-3Mc@~tOd7QJH!t)oVlNAT@J-(*i*R~;C>Q?zP@JY*joOjcw0YKi%O zW*9F0&+z&!|H<#L^F7EOWrZ1xpqcL+E9Bnh8_>J7^~^H&Tvg}EC-cwk<&Q4Q+HJi< z$8cxs98o)&_b*ybCmhqYxN3ATuQ}a5?^1Ya^;O|>`!8>tx;<-l;S!UWG_!AJYgbQx z_M%Eiu3IyEd2h_F#fsNv&6c;`BQCR~sZZ^5-qne3JeO3d&#c>VZT(*<$v^w<9i6*w zyYkaIt^8-f*^4?lzPz8GH!p=F-ZX(1*ZChT(MQ%*PI`H6a!iqq@2b;HXL+@=Dg|G9 z-IU#Qdefq1{~5Aof4O_|gV>MNQ=_(;1|OYx>ch8>5wmjdJbdeJvP*VStjRN-$E?@a zJbFLH?%u6ak5_0`+tn}6=w7bh zac{Fv;JV4XYuw|%bIQ#R$@^8gRLo|6)LYRh(^sZ=alOb$N_C!=^42mXq_a{`?!?dQ zQ_Eb-ZYvgwr`_GL>PvpaIiWdzYO5z*VarI^)-%pxuh~OZc+9+=4I2ZvU#5^+O_Lr z*u6{ExtnHYX<1tR^tuw_;@5p|_xdWe<4f-mqf`vQAo)aFS$%l-NlerV0g2uZJ$^1k3 zhU-dqPdq-nDnevWN+yrsn$4G=Ox(ElUFq5roj*d4_t)L(+wb(v$S35(-6NeH~xHFvv$@d)8udNs|78EOGCreU3{;W`}XI(bvs@%^UlUOvM=`EyC`+_ zNpeKFuvOyB`!7E3%3L??*6vwKx34{Sd7-@a{!XR3SKa$(*KU93dwt<^H?6xTZ}v?+ z`ewCYQs+)9A9q>xSnIfLOS9)OFD=~?*3-3Y)3TQZS$6a8=P!?ZC37^?rC#qvN7srK zv)8>^v0LfQZohX*-Ns8};v!;L|HK-{?zk&!yh}DmyTjJv{HF>-p1gItqNiM0zpAF_ zl9k@!iJ|f`b8c(Lbmu{)<>HO&XRU3#7!dX{ zH_yGQ>uqtPbU15M|Dn|i%ZiFE#Y^2b&z&n?d-v4aZ?C$X1*8~@5`B_ zq!~B+q=T1ao%~|0aOw3qZg1BmpWUXdPExggIkoHL!--Yvv#!2A_w;V)-N4iPPA&QH zHF{OAZb5sS-94qdFX#C8+TVL3d#J6p|K+>xrE{+zA1zL4Ebh7derogXtab8fm&>xY#)iC3m^6)R z+esO(m!gv`*CrL!t)BU!YhK>Irf;jS1n2MaXA|m?y*>T+E#*Vkk}rHOT>V!pv_E=N zc22QT-%DTFSSz0^+1J#!${Y=GjoqHL= zn;-u8`#!idK5%Wht=C!IDbu_sZJQ*WdiTS|wKbcT>uXOu(a~|_rl3Ge{>0ajt2f)K zUY9nT8~W_K&1|`oFYlXVmQ3Eha%q+Fx0|o8FFH1_ZI$gkzn44XgcqFuY+H8V!|KZ$ zcdgVDw_fA(R(;8~pta5q@@~KTx1#f6VA=hWyXJl?!XqT*gy$vQYEScir(T?!YHpsc zRJ2-N_?v5(?dq%ItEx}mo4ug(O5$}pm$3rnS& z`dayIpJiPdvzpiT)~Z`SUMwx&tMv7=ewEtY@*C5x@a%iN;>O+iW?K?O`&WEi`%c{7 z^Q5>*))Dtr@815ZI$rN^rS@Jav;4-?Z#hakuW}ae+va{rS?Qjso6_o?bNBl_mFj0! zy54Jl%t(8lX35HR(QDUk)Lyx8Ny+-TmkifuC%Jts`m|HDdZN|4#SwC~>_WGq3%2+d zwe5OvYlga4zUZ#r(=k6qMFXXU%kP?HZ;$=vyK9x!vbQTF#pb>`CcpB-HEZb=D^p$f zzBskYYe$&o!}}$!VPAcxr@jk)_j*I;m5NGxL8;=NxK(TXr#&rRx&8gabK5Si`FJU3 zx0c7>i^BPKx8JN-v0LA94S&(@un%26oVTX39{qYvOh|JFT}v{NoQ^X5Be>f%iYb-r`Na&22Q zS2=0#*<$hYo5OQ=>7Cy6C2+;j^heXZmA5&TF0XyGNm4K@W%+{QX;Uq)uDq?a%Cu`$ z*NWMz-fnt(mF)`Ov2{;o$T=<*xv6?wb8p}c?JR|9TDP@#s#U#v7q&{PJg<9~R$gCL zT%O^ z^Z5%;iQmz^9)4`oVfW&tQI+aRN~?C+t?GNxwL5>`iPhUqtm>HS(i8&Ea3F@lo9Bvq z13ucf+)UaRcKOySU8B>%9=E1%n&+;ZHGSrr`%^trjShT^p91{D|Faao(S`AEqsP zwQ9FgRrspT$IKRIO4oB8&aZbiUV7?vMYeThMs7&n%2{H$`;9M}xqS(V-F`pS^>(VO zw()|m8`gRq{`{L~r<`?Z_Djvs=#a^#NwvlkEvKrum*>nafA{@}-(vo9;i{CIyZ!%~ zZCiK$;GcqNCvDj0Up#;RKf@uj1!*_^79>gJS)5t-x2j1x<;2mZF#XJjTu<(&upG$ zaM5hd`}J2j%lf(|-S@i{rB&w0ZJRAITS-zXvn$yn^Sa*ks~;UVRl?N2nDWl%m5N-xNygfGRrZes{bJ?Q|> z)~|%y`t%Ar){b4<174)A-gIZVcgaPwsajG`t7YEKyZSrSO+Vi6c<<{e(aWQL=zWN& zzA<~ZnY!2FdRalX%EN!R`xUO#nQqZr;9bCC?-G)}EZ8EcWTR!<5)9@3;-rwr_y+MAud6%+$!%n{1Sk-aaa7ANxb(7}O5VxN)>x#u+XS`b*dwKno_g?e9 zxUSe4cE0!0#@%6RYfJ@;kGy#JpP?jrTG-V>%az*8!?b2fFW<4&IcUcKXt-ppVWxD_wnbhJ~|#!`k$d*+`s3R*C}qd zxsh+LeESuXr@hP7I%~$-g>7!8y~}e}oLed7wpDs@)u+YVl>0h67Ad#wOJC+{7#g0n zI&!^VpIxlYt`~2z;--D%T=DI*yqI^x*Qw?#Qmi*l~` zUX8lDyEArk-5a+n$6kI~*n0Fq!PZc-m+MM9JFmN&iQTfSxP5Ng)2AUvliLpn`A(IZ z+pBK0x$f!H@bb#3|9)&=UVC+Y_<{XvQpL7ac#!W;2zl$JXahbnZHN z`rGZwpOw|W-frI$US9ii!u?Nf{~5OYcsNy4TG5hx{Up?Gx{`M6&rB+{` zUXu4OcjCN{K5BEXJ=;_IW9h8d5-~RJ-J4%uvfuB$vgy{togckdoB7l|4A0*h7NwjT z>blLP^JAD+@Mp_Dmyn!}rz>7>d+{!5+T$446U*B=leBM~UK1XZ9pd-4YWuGpHg$Xk zI`L|k?i}52?P@YLT_00=TPmAYXzpkYHtV-#- z3h(!Rj+yIb94>kNDn8UzEH8ZKan5CG*6F|dy?zE?KIrK$s;Z;YB@uDsac}45h%3Gm z1%-DC3Yv%zb2G9i|VgvN-Ap7~Upe3k9tuXZ!7SJkW5IxpV7Y4st&OJ`4Y z^%YlEmc_TvXvv?bn;0c`nlE8i^Nciene~cYs!g+&Ei-AeD$~-^SIJnt-QCjqn_s%# z?R8HUS-EK+`sMa}*}}7>Znc870v@;C)u+1Uy$gxA*SF7pU!v*pBJ-C&-{#jm71oiD zzAVoAo4ixcrhAk6{#$GPAG!)2bKiO>C9FK-(Hh;x%bu|z(`CPfhQFJw{GZ{so!@^3 zqpk`b>Gsokwv`Xh>uq@&zT&OfsndHloQ+oLO-=}Hn$+teMjN3X~8_!KBHdUppzRr?Toy3*RM;f1_Q zu6r9-TgS$BZ&`GA)e($`6w?DYNP+?w%v+q!IZA^K0Tqo<1$v7-~Pz)$m)iy4d>BoRZm(w)@<4M zHZJ7FbF;}agYU;)uWxm!I6PS(XUiY|S21DV7R$Xh5i(rsJ25q4<@EaYnWy%*x)jRJ z6OhcWn-O;EihKC!vK1ej=U;QJ*EVz4`Wo^ydq4U!_Gdh^WD8He=Hqu^jW=uU zxn=XNJX`kB@SSI%%>7D1g^=$PS=fD`tEv>%fC8BPj)4J=hTvYs-zmV_sW-z z`_FV8+axJ`Y0k-)n#T)HnymZJV7KN~o}g)xmH&d8+;{6dqo1nQ8y(nl@7E%?FF&}B z{glj+G;^Gq^(;8+(riB6oRynZO1^hLJvH}JNT~g_#ZuR_!&4%zTCG|>p?g<}>zfM2Xy1ab(BBj#LN}_#C20M42dvNSRUua0~-4pXZRrnmWcS(zJtoY{Z zap_l;r0&LJd(JH?y(+h*PW;T--7fi|{~2cAz8~`a)Om+*`oHqmRQi>+KAAc7KLf{- z(zYiz6}^jJCCyqo=j^12$#cc$Rw~w=y=$fY{pF5JF5h5Vr$E&_1B2FkfrzbfxkuEL zy8>h7^J-6>zp-!W?9WDeRa2)5=l#9WpLh54zS9?4qn}hq1s1Hm@YZSNMV)!6rL*1} zU46WrQ&4J>)6}FSFWzlQJYp{s#MW`yUu;$)2Nlxqf3?@ z?z;J8v&X6IqmLf@{M5=;y&EXio3ype-R;Y()vp7~eJ`^6J?zXltEw9NKKgCt+Iy3v zvSxnaakv**RzUmFAEc7J&ueO_>zDS$63zw@&~3(436x zmcl!uHo4uMwetDRs#1@`TZ`rgXl}LkygT>qn$V(sKhCq~uAAkt+x~M`UR`SS)R^uk zZ|~mOH__@f)1^s}@MB(dM;mM`k&$Uo|_-;8~dkN zq!oYd{m-yJd|QO)n_~^Q&-As~Gh1@QGc&6_`D)vo3$|T1-aL;pu&gM5mzGw}+9EBb z#S;TT_rQp9Tz54+b~Z+C+PdpWJ9g%k%y8~Jcx|@UroNczhSN^U{N0_?ziWA;dhE~2 zz&R)8{j#3@H}}`Hy^r(THZKp`Ju`mVN13EqWxKxIf15V*S?fc|XPh~gJFo3KpR(n= z$dwDXo~C5Y>8X~wuG3R*RGGDUSKhjtq2>4Mqi0m<1&aM}Db+Rj+4{!zU9oLrL6LE2Q=i+yG$H(_lc2|DYvpzpPeffWeTf5@6{C>SX9G;F8%qE-3Z&F-4tUiiA4-}UCLxtFhV z`{*T?bGzK`=vq0ivp4bPp)c-S2MWFS>GrKXWPX3qB)9hRLR1r*8k9 z$67tfd(NM)TF<>+3f^5UxT-H}Z=mRIeSN>ZUER@z=BE8R)#8#dk9JQDOv+rPR({#;UYqIRX|nmdUWXnI z_|Wb8jK{9++AEK|@^ss);!)c_eLl15-PfYet3JKy++3x!JEcvmWA^5vjt?a-UsgXW zf3=uXOy4K>%vAjgw$JgJ}Z~7K)yjEdymB;PV;*`+riDh{w zcPVKtQqo$bq!b%_IIw8#-j8-}#=JA*4oAO>at-+!D)r*~mh)Z(chxp~rXR^zlHRgc zb@i$hleer`HBmUFr2pmnHys^U?v}aSEuXvEB(hhtBI)Dw!l`fX&XZAIBNnsas4%_aRAx2_j03EW=d(rcBodf$`W+l#qfURv+iTPV?ebMC9s zj$03<>Wq3y^v>s9=`B68_0!#~dD0@isiL#IW`vo}$~$H8Y-Z;NwSvcrhS8@*=4VQz`A+A*Keb>-+{WK~-bB30db4#N z->+$_lycSH7U{eAt>{?&{LZpwr2{3eMJ`9{cYO3>ZBpNRlUR|bSbA{fqE$O0--`-`gdC3hwtw}FW7<(`vddgNZmrn2Q+(au+a?#1Cww^B zyy&27{Di0F+^RaOO~kBLuiDjQ`g)VH;EE?McgsF)|FY`c?#n4Dzq*%L^eqd#;8>X( zEAnl5alqBvPd(C>f8D)x)rz&ZPv%#Z++KDx%~Z~`y?u_l{K>VJJ>u41LmyrHsZtu7 zn&tEBxb2+TXV&GtK6UlxihChn-kC_MuJ&D1*6XzARj6rf@w)O;885SxYI-81u9=xF z+PLCMk={k~y{GT(Z(Q#2W6R6drn4)y_xdhfy~FBd)v{b&7bB0O%d-WQUBXNy`doiq zuD-MP^$)51E48<_+&Y?ZMf=>+;Lz*4p4Tkvo3=>(#L>pJ$;nyTQoZglYW}> zRoFRKt=kuP=lwO#yc17$x~8x7P+a>gUNa?W>xtd(b}!25234V}OeF6qw?E#Y+b2^l zzTK#=jWt|ZF!WsU^u6bb-mNS86@Jg}iHr7uiXUdvlA~(NPvnqBOlRf1cM%h% zx~{!fo}L=|xr)WD`N-)KZvIo7W)@52ZGSC#Y31CxDch>vvwv9j=E}|ATdOaw=svye z@9SM(g{At>)VJRe7wcACvf)bX%8XC5tJiKVSs8HrxJcH~bd#)S+82LYp58s@$=%bJ zcVsdv9o_QmWXA)~XY_V$p> zsdiKLJ+1WiHTAk`lrl@7Gko=(zfr)+mm zGg}HIbO@64mStVNU`{;}&qYu2kRaV{Yiy%h5${SC0I$++CHm%Y5rpf!%K>t}Bk2ZJlyg z)aJzA7hRpb?q|m)_dG(g>GGV5D^V~g!RGn5SX+Lx6Up+nd&%BVk)osm3 zPgT?(F3MR{vyXf2uj2)4)6-*KyOuX^Tf0{<$mMC=m#q9#E4uIPt?KM`&*!{wMwoHl zuW1%pPZpj?%n>d7wKLk(%I!)~(N@ttue$n+^o{54d!(dL5g#nZ*Atyxq_cI$ud}yx z8};6OV2%B`v(oI@?YiBci}SA+%{DN)bKkRI&1cCaNA<2f4-Sf+xKF=Uc#@{Z&*)Qg zXT4d`dHQmyt6%5k5?8M?S%w}v(hlA(*&6?9{ZH52M_0pTqxD3~MP_A&nVVib_ODF4 zQflpsUYEGlyVm=z+{_fK0x#L9dO#oCR0xB8Y0~j^GezT@ZfD;47rWd2?`pyD+^hax zx>t)zmS%Syy>;;7_Og1Bt6dMv_QZ+p6I0omm##B;_ulaH+ZWm;t7jcG+Z|j!zeQEPf6r6efTKYdj-TIsj zlW+Di+`)EcHN;^M@=u7n(&CAt;%64rH88M0o^^d|)U!p4mYwRFE6TL~*v2(khP`fE zg;QL7Co=rvf@mR+r0ymd6gTi++j_ppv^QMqi;K(M5KUvjeUW+k%gwv*oSn6gQ)9)& zd%;WcweQS-wdfD)*)_JI=Qi(>s3|K<=k`0h^VaIC&wavXn{GSlF{SKO+F_y65YVkj zp>dy8T}3WR1zk3jn7w1xw|D0@y_;OYK-G{(@d8;KYLB}z-r0Wh-S*IsZMRoxRo0fL zO6?SWsOl>k{^;jb=dM!T7I!5ocjlRUX*Z|tExY;I?drejavR%r+j47dUc+m= z?Eb1czOzkUVWnYmX!;-^WJ63?Ea@V zxkvWotBTN5GCBoa%g@S-+f

g!=L@G6 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

6oey@#%Q~EVGHsQW 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

pX* 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 cwIku$70CU1>+yDRo literal 455924 zcmex={G?Qd zlmiS542=J8Gqf-;5)YUVfEf;;+L;&`nV6UunV8@b#4`RLU=ZZsFkzX?%qYmfB*@4t z$oT&VqaFhz10xF)Gb6%B9Bk~IT&&zo%q)zIAW3FMBuRD_RyGbsW=gU9Sfa;nhqH|2T$2@#WYkRVn*b}q)RF#rCX;bho~+~*Jz%+ zc*%09k11+5Di^u9x}~+LcdW2_Kz#10$0lGYhMt zp%5Fpqi|qip%O>q#EnKpA|DnWbPAfZN!hq~^5)AXAthTxO-s$h%_S}-HC+mxawSxZ zbCF6|*~3H5$%~hyPQ9uYZt+M}@?-Pi$1W|dTT_-Ed8(fF{}uxeGb00&AhRHYJ;U)2 zmx{lNA;N8xH5vk=Aut*OqaiRF0;3@?8UmvsFw#SS@#htd!Xxbg3m!SI2q;v%qG2)N zm57W-DHorLRV%xYtW$F*-$KVx1*73WdU|lT4DWd|z0K9TKQ2b9f05U^Wi|hnzY?0za2^-F~gUtybXK?}n4slZ^C^FPOjd zr^l68g|fXCrx(>M^ZlayNq)+SkbgSoA3l1w++Xc}bY=2?2ANO&wRh?ajy?YHv2wru z+d9dtqk@wghF0#DPm%ihZIU40CZ5xhdG*=dY{r+K=s&1tQ(kyn^r?M=^DBcn_j!bi zKBxvS`{!n-yS-YiV0&u*lqHL%Pu(`L^5Z>=w|6=wJSuFxbS&Qg*Ewg^uEp;0uO94J zdTaXR{|qb7#N@n`{9g8<;P{Vo^SSE}8SHgmDYI~Sn7(G*uH8CE?|fadGXJm9kAQ+> zNxS({CD)``mAqF9xVBq+>#J`%Ix?L%Uw(MBdd1GDr#JKW7wU_I)=4klkZ`L#N@vw^ z??Y4e2<>;yQlTY7rU!RcfVb5EuMYna^K)A|M-_7UV8KD z7Cp{)KCu1mlx2VNe`qAmyc_oH&92qrI**V3XK>Foe|_h@{HhhPt7>!Czp!TaZ+&bK z`%Wk_LSMqOd zOh0U?as9}uZ~KK-tE^K!67%iBtcjCvge3)rU$}B`#k$;1^{0Qid9OWCURkwix2L_8 z>!V}MQ;Tz-OBa5wQZ_@dyvu9s?}$s zS`&M%E2hP#?zk56@m!7HK8p|2zb)&V)@JCuc*UCK9kEt!tDB{m<(yu)<+^4rk^Ez< zx9DZw-Q9r?w~9*q5L&(=L9+7Z&hOQ4USH3T`>A;!Sl-!j9cwIWy~2YS`tyH;wk}F})tXa%+7_*Hp8e+c=C~U-U6~WHsG6kh@K9=Je^F zPBv!$yrRWd-umm3c(UUc37IuIsxdJ&Zru8=kV;(=t3T)SY+NudExQ29kQc&g(jk zXq@;YSn4f!J9m@u%cpj8ec~48i9J4;t8uVQ)x}LRa%ScAd2YG4r?21pY0jJ_f5UED z3VsPj#E*o!;KaQT5d1;RVyxJ@ukYNqpxcupSysL9t(7^EJLyRU+oW*UDI!9CuCte3 zIK9}@?4;|(Y=<3*FLy@HPS}xKGx_eTidz4e!#gL>E0K%zyXSlEyWMP^+l%Lan)gD# z-v)->Gxg-cGyIcY5jNo8ebx?&MQcb-k(;U-OCo%!5;f zCOtk;p6jglN!GaTtTBJApqKQ@-Qe}Puw4IpGx-1cUC!M4r+T)kzEq51?MD5d;{J-2 z9D7<^Kd@fthvW`e=oM zRC`vlyKGIHZnV;R9)EBA;WriYKF3>bUbXYqbTM)LkI9L7<>s-oJ_f(YK5^$iL$BAc%+>n>b2p>0+sXYRJ!UIDjr7j$Yk2~1L1$l+O{r1Zi?|D?)_?Sec@HBX+)-Dw_n z@7n#=c?kt*fuAtP&FwpCe4(ir6kNuVtz}D3Zd7SeZ;Xi{I_cs5sC$kq-x?Zn3FM6$%bM^5=LAzH0WnrH@`J$rsH2u4&<)~^qZBklo zTYzWgzU3EfecdY8smb{)0QNu1k7W8$R>1?bj9OpJ(plF1*R1&l28# zSUuA2y33>;N-K_IhV4O%p5FI^B2@7?g8TS#Q`TeG_LxbVYk%BzT`KhL_2l&>t<@?A znr|!TzSh)Gelzi_x97L)SFuypJzoCuyYafqrBmO|KN4r3T-o+{x<%rrdHgafi-UH| zFU{M1|D^X;C)>T7>ayNuYfD@+_3Q|CT5x=Uacnr_CTAU!xRY6SbDwnb%@0@cs*O8& zTr6$tieEE(9-W!$9(S3Ym;x%o<}_hihe9j|Wx@YFq}UF4nQZe`7d z#Unhz9VhO6z#=i2gqSuRD41Q_I^|l@my<94T(>S-s;Rb2d-YADkkg#DJo}aUZvNz6 zDcvU(5O>l=tFUlg>isa?_tp0s%l0>)h}`Rro{KP&9H@$Fpxt%DV}zfMj0 zrk47f_lRaeOxbS91${ma4cDEcI@OhOmVU7`erDkk`(~$KwW|D#?nOZ^Q+e;T^ws-mutb@`$%kJyP+ulHSt)44W1x7QgA<5^K#>B$K*O{clu9=z|;I z&&BBM&$@SC>DE6X!*|(N|1$^{eG6MI?tNjqNA*rqiONj1*&o%?LOGl>ymOaoFKP=lxFR@}jW9 ziFYj~g>RL3=$gK=^xcza|K!Cjbw%-u9ZKysor(WhcWau@JNw>N+w%Vm%!>auJ@dNO z-rFkf_glU5$JHHs=k0m)*Ciz*_1F%qDTrYxbR!w9EW&TRFLRH7JNKUI&CjK8$~MJ) ze)wm5O3Iy``@Vm&e=HClZL!1T{={|e>p#z`398_k)98TRyF;ORoPNi0hfB<3<)wu# z^K(BQnsPq)Gn=Iwe}u3zW>i8=pGJv{X;zqFaw z-gExkAGIe|*H*YztvGSvbT4{~3>qrTERT%L_dq#tN_9Ew#=n*`iq3mHl>RQ+KJC?> z&xZZYPfUaJi*hoToH?!Rx%gQ6ve!bpkH1~8iY?eoxGQ{#FxyRyxq1g@PW{g?_07wh zR^QC@R4(N&e}xi%`U*XO#=`4Ttr^{n^)pO$x?jjEGAwYA7> zO;giOfv#kK-ib~UG ztI74eUHkOQ zXu52VcX{&NkLB}HF4vgcn(gmn@qUpv&xwp*+dgL&vj1l=b-mKI^OT-YVfAI(WgDv` z#ggmpZ5uc8f6BIVSy60zM_TLD=O1m??mDS( z^GVe@rb4MVbJv6gb1bXcIFqw%=XJT@bt_I>$y7PF%iO%D#4Ced5SZe{C=A`u2+^%_&UGh)3LcT z-$$42ec4#Kdd7#|HJ#SeKjz)3i4@#*sW?k>#j1d7`qSUPw5ZwmgiZ2zPtL|#-}Pp^ z%UFBE%`+#(9uMA86nX6Za>Lwdx9`QBPrIzLW~M54nb(EfUGrW{x}T~lSR5xN#<#uh z++5atHsgma!BbWqnv(L`T+`~L`Tp%`hcEWKzsq>mm-Vu4>Wa_nTt59=_f2c>#Hz42 zw}Rd;)tCQKc05@%oqOc zmTi>_zw)I|z5Tb;Jhpt>PQAeO8(*Kkt)KmBn~l^CySbT`*Q;loF_v9-J#~vBi>Gey zg`fOWjZI=~_RR6q>E5wzx%$0(p;Oi#3n~d03eDZ6GU<(b*WZvYTfa0v-M9R+y55UD z2k*T2A@yxxZ_@oeB{#p9<)q%*nk7?i@YJ&4kYT^bQ_WLTr_9csRsHsc#b34OcO}+o zvzE?yC%xUwPT4zd`%`a=(1>S=g@LgGOa1CXZhH3eDGB)5yzX1S`D#{B&zT3g*?bmK zOaDY?OW=Dyr<%%(g&Noc2@ zgvK$O;M_}wJ)s$sqPbY2&U`s%-n5**;`!;E<_Q}r+1LHsAy|-=o2xIDeK)VPt2$ii zUtY%9&2#sts~@{F`(^Hx<9d^LpKQPTo5^!!Y|6dapT704zk9lGZTjBc6}JmNKbI=B zynf5ebgkI0AH5P^ZoZZe{N>tsdi$=*760^i&#yK!zrXim{>RmU$I7n%I@^89M(@er z<-FO?!@B0g_bwOtar5w-)4dhn<-eZ#z>>4dcCodxQ_-F0+OCs-?0F+O>*(4u_tj6z zHWfzisp(iOty#S1Tgqaus`66tX(n^|?_D?Pjy8p_zx$6-Pa|RN(WT+h1s6*uePs#bR+Ds#S`n5zi%C#s;ACTU8hy>bIa;=!Y&c{Cm<A7g&^oVa*E>P=^ndoPOir0?Fl`h>v^gY@d;_PFG4r`{>u z_V6!Res*g4+>mCIC8sT4{_9_!J?qJauimxqjE>J)c~s-)sQ}MwLfv=now{%}-bZiA z=k?xJp~*8X`OWW1UHLkvn(ujW($`$Sb3RYyc)G*azMZ~((5&70kdHx;e-T3*k7X5CfJg3mt3mDcx7p6hpd?rUGgM{mli zYo}`T>plPHf8k55+P$!e$-DO6U#fn*D72qtuBPS5eDADK)5{l`FFCKOb$=z7*L)>6 z^p#Z7rKi>g5j(;x@~a;m5xKK*!)T;4Bcy;d-&zV&kLWdndE=u=1dn|K1DsVC2O}X)pokDC`hbTP*clmQks`n z$FGUgC!Fwz{$-I+>(?U!fh<@r_GYgc-|S-Vx`%&$`E?qz9*gD&T!AHH>Ov*b^UMYH1;->A#3 zd_B#mQhx5%xWJR^_nDk^`)6C7vgYm-Ws?`%{51Xax>hcCDScFA9^m=-y^{H|{)w`N zGp|Lv>mJXHc3xGgeIz{P?L1+fXv=&%%!q;W5u-dkvaBeKgEqc>&09ts(5ZaJ$lZq8}EE~ zT5VZ*RNwOQkr&%!>*AE9Y<9dqC+C)3u~A%DZswd5HzktlmA@n(HJhxP_E9NZ*yp&) z^~c}Lg;q_N>>eV+?78yX+(%!_=Y2VUbk50%9IK*s>V2~F{CV5PbK743knf8x>dyTc zEZQfnv*+}>!j9r_7jvwdl1@lMvy zYpV6Sk8-?;=T!dOyy#)t=kKzYU|JVGgFql zdcF0>Z~u$z{w1@c{B>cwey`N}hu_cbU3>V?LQDN`N8Zifq~~Tjd)9uv zCx5M;pWS@tz5Dx{Zrwk(i+`w2JucNE~Gd!x=5dJm&g~UGZd1rW>|LpnCp#6H@ zd*NqAy`@Kls*a%-J_Pt|K{a}R>_5s?rAO;6Hop-m$KU_j$|vofPfggWO`%7BZPM1( zn0r`k+dGd|pBcHKI={V|jrRmEsTJ}tiYq)Rv$FrJrTN;Yv2$04C5wA3jVv_VzH6yh zP50WG=+fv*voG;SzL0BQ`ZOb|&a7S?GkPl`6Y`_q3idTpKR5_GPZ(u0M5KeJv{u zzfR#06ncDF%q{koMDFbE_C@n%p59hyFM6!5XyB8SZ)S9`B5tC%-r6@y*`VVWHP&YOl09_2qKFR?&NRcU;L`yC-PQ@-|0_ z*;fdJ9ELQb@{ayR%0>ATe=T-7doup;!#6RlzgAYtL~VPQa_v~Fe|pKU#n-K^wl2Rv z;cDWhR?SD=UIo)KBh@#oKCb_=@Y>dHshDMNkA1mS|;hg0~V$(&gq+ROXdE`=W%5<*6^+M<3?Q1kG)*qV3 z`i}Rcqjpkyw7ulab;?(t{%mu*T=-h%R9WBtqicf0dmbBPWcFE4-MrM~R{FAo$NrjL zE$@m_UfCHwV^{U!j6FN+wMFmlUg6#}dCoSiJ55uSMJ0YmUGDCDTU{RPU}gMr;+1pD zOV@i|Fn#gzht2%jl+`nSY}3}Bdr!^xbWHjBQ*ZC-eT^<~-4a>+>LJf{*E5k4chB5EpZn(1y5k?^|ER6|>O3uVadv*t@m`ZZ$DXX*WnO;H zPHVrNUD))kDkt^`PJq~hMuJDAt}=)^um&)OFoa@f1n$ajc=pt`{m^yOJJ+u~-PU)* zQcUZTY|Z`Jieo`9(@lb=-}*Cgnb)yTr=&gXmP~snsPfxZXm0<@vc>QBMSowiud}x- z;GfT)AN%8iy1uJ-{+m}bYiIG=^)qr@b$rYHH&1x6V(M${d)t#YC+C^1jh%e&?ybGM z<0SvomfXy~mlKfdkz;T+UZFJCcgk|NZG6?b zWvkJOmv0^wy!VVdt2b#%-$_ZSUZd$=CFg6ir<+Qyx_xZx=DT;+g#Y5Ivpv@GpJ9f) z%|E4=Y2vDneUBT*ToIn-n|ti=!`+3ZQEyL7(~g#@xf!}_YO!naiO!rKcOK5-+?n~U zF^2cV$scnU*?qR$bBpKM%BV>xlbt6N{1LV7JJmB~W?$LlHvvmdUpEz(%iX^G`k84m z`=YzMAG+D|e4flbIkEoYZSy%I(WkbanHE|jCfc6;SNr#LY>U>iC&Tl=%>#3Z1?=G3ZmN9rd9&+(_EbKK_;qaa2}z;prB!!~bymIc(~aEsXVW^nx3lH*{$?Bz ztJ)co8h)@n_;1P2pR1SdyCt{kYN=(|jjg#p-}ly@51XoI_sZwQg;S5yB6X9OY}QvV zzpzW=QtI52=gjx-Tut)7716V9|C4nRzb;RSTqQSa&h39AZ z^LuyAZ}pZt`r`7BtJT8(ru%;EFRgBT@K$f(`f0Dt=d|DYu}$kunMygIW&1-vx$23Y zfhxbRKUR9RXr6KJbeEOJwiCa6)0+A7w`Ed>-=(_JmrGx{pR4CpeKjxjm(r&-JKI-% z`^xEREmdanE;lsx>HRs56~Yhao?QONcXrv@WQ*z}XB$u7T^PG~Yw0mb=gD%qxf8Zq z+icjqdVvp5?T+P!Q@xIFytC-Y(w#3o-(3pyWxw#-%YX8^eHpvd(v4o_GxLQ#u00mK z=FXIim9ZOp=9ay=Q7wMu--=JyoqtNnTtBm|IRB;D`^e)Dj<&A6`*_FFLqGEFZR8U@ z^yWN>lvRnTd%qm_SEdvW6jWy(OpZG zerHGJD8K9d6;|?o?Vt1YQ+|dEM;^OU_(6TTW?Iy-?YXyGZ;Qnwy?J_n@{Xr}!6LndyR?e;uORI`&mTI06{m}2=4N0-W(2CVjS+Alk zgQR==Uawijczwsw*DANpd0J$9ow)5OzYdvn&^U4bKG~Zw|-Mf7M$F!ru_U`}s=fBhWZO(7B z|4jR*c#XdwSNu69=vw{M{%&^5{I$97|1)&HIv9U-{(_{RdesJo@}EBcS--A&kN?b5 zrq=~LU&{vSTGStUd-?3%=FIh5-c)hqC4cFQPq|r^YIJQq&#{Foz0-EiuC~hZX**~p zK3QN{qI;V1xjjoPHmhtpeQtaG(|^%dc23uem{ryEpkHLU*%hVTT31qDt8Y}9T^;x2 zR>!ZS*UovqXxG2>jIU^#%Qe@pS?BCba_^kntNXd^VbR+e1yS8WJ1t{+k|U0-D{e{E z`f2)*bzjBg74N^>J-V|eW8Kl0G2LvcrD3s~(^6)-er=l4wI^L{&D0gyH#Y9x7@Qpx z^!SazY26*a-))v%9_#u$;^n)w3lsbU&!0FPmb_8p)axlrzPoAf3e7(?DKvM<>_C0r zTEkzJSMzd@Ke-ot$#PoM++Qcpz3q{R+?d6+d2ijLD=H83xTY>iwe&nD_RxBAi)!a5 zBO_&HW#!M3l9KXkl9|n}np_RO;s4I6BI&dJ2L?0UwBA;sh;!sGM8l=e9}tF zC7s{+E%V*w^`Aj)&4j7n0`*q0ytun5$oEiCi0jl$nW<;0u86*gDd$PwRW8pG-y42z z(#K=E6=&;S*)QdHtxB9|YaM&_;pBDEH+5n(e!uz>m1}XQ?0#`t;nLXM-*eAR$yGXE7%sQ2Pafq7C*#c%wMim$(KRdr@##D4}OZ=t;s zPd=4A?7Kf#}eC)vp##+2Oj$^!7Ee zsZ(`#Jvz1V%&A4WHPUl@i)X7W_1kg#>WV3g`eyd&=9bSeH~3_?{q%-P@yF6POqW;Y zt<`6j%CrbSyv_9Hk-OSkW2L{We3>7)Y4PIB@chg>*WPMYpEq<8-f<}=Tk1)=cBKs8 zT33s>DZ)G695bBHXMRj3XK7$`wh!O^%r`mA5y_Xb6YsyNxh;C&#nU@`wQOCYd-i=} zvyE1}<|0vM9I-Yjw@c*L&A#6&Gp1zZW+{1IO-#CT?#$NK)TL!#ZyeIS)_KlLbC=r7 z@NJ#D%8ZVmxpT1f)ST@rirp4VPrm%+j`Yv)i3?SZ%XWzPATSBe)VT-yUd%HH|EZa3HW(u*{xhlEpTD~?Rq6sN&cnP z%Wdc*{YddrjfBK7Z2+qm{n*~6l%D>bYw%bz@3TBhH6R8MNxn}x3)xwp3K zSXQ&KMRq+l{q>(A{A?lHvC}v6&n{ZBwfmNG<)5HcI+8!dLgu}h;%hIZ8Ls=~yZ5w< zlX`73WO~o6)z=KZHspIWsl~bb=3T}G->tq^5pnPskKx8 zrBrO#uko;`^z@8VbByM#SbOqK=o;nacfE6imOKr0-?Sz0eDx2ePx&zyZ#;?WwmDS3 zrRva}-#eaE1Q*CWE75q;u59h^pKJD7cEhyCoCSA#%6V+H&({Cl=+JI!NsQ}cLS=~1DeDHB(E87b*4o+|0N;;6aWH|0l9Z-?cc`ufsMY@ygw zwM(9=VYjP=ytDI@+>$MxKd3$9d*3^LTH2b&R_qd+(k~?%Zi?AhY%^2v#oRBR9tU17 z@|+nkU1g@pYjLBjv@I@6zjOO1Z&!Tr*Z7&N*RB1N4|XPbt=_X^PR=LK^k!^S-#pVF8!w*>f2vsb_k+eWwx!D_PRhO+QTBU9yZHW@ zkEPO(F!`a=QUMFt3aDIAbeKSy`$jCom_G(r9840nwZ_M+xZbgS^ zE&sIX{-cMJ^AGs^X?*$c;=kN8eY4LLoVj4EZsk^~WnLItyEgw;UfBJU*4q;EtfhB+ zyUhJfKt1IA?C?yk*xb1}J59ftUK5H$7jAib_V$iB>0+0C1D9=Iwp;40+pEV<9;YSWK7C{-_m1s}l7|W=O;P7m)i`C; zofSAK^O)zB%f43Pll!8SRi0dXdgx1;?8L+y`$Fe=ZfthWoaeiH-La{O@&1WL9YW6@ z-}vXBa%8sH>S<^4CW^J5h!R+;F!h#pU;om$Yg?tO(rn%y`g`q4%$+G&lS}dpC*Stc zoqfi5rOn@EC5I<{u6!PO{LHCK1iRKmXmF+4>hP&CJ(n#Gc&5RimwSKJCf6pd)owx@y;to-mze8uUr?!LFT? zIB%|5eQ`#?ZFQM9W_$Jrzg)L}Y3BD*>Fb7hQJeWRHN5gdqHhIl7u}d!9d_hp_=f9x zu3sL!fDG57)Pm#9*0bmQY>RDwCAlM1=|fMRo%@6oYo<$|mdo^XUh}yYdPT|2 zy7y^WUd*i(Pizj^3d}T1w978#Qc*qAKix7p=vhtoj9=B;j zaXKznm*-3SP+s&Q+2qKFz59)Mc9okQyLYniO*_NpJzI}GGvzw@>$KN=&l4N>gq<`u z<@V+~k$=&5$!5b=)uU2VrWBru*|@UknCqTe$J^5@`7}=5p4`27ap;t!AJdLq)pXnT zLstCtc9pN!-A`Zr>0~)gj?Zax*|`|q=>2jUnunhA&c6QOZOrGzoP{3?qj$^+@;oKO zQ!?{a1J`2xnz|1mPp2!JJmcPN7_;3i>BqIhtLxsi&9^vJyu;E+YjUy~kLKk^$-6GA zuS{H>rZ{ECkH@C0K ziJjkdGa}fpSIYZE*XO=NCBG--+$ubkt3J`xHtnCEotT??Wq0}Rxv7PrZ({;~US1O& z(H?KY&mtMc=+V`2RyG_dj5|qx)5cfP~ zaK`qS+_t8fe6H`fJT(pOy`5bvczfA1JMXJIlg{qxc^Nr(_M}3;+mjc2ZJ92jxHoLd zy5~_jYhN$AH@VjO^|Ix&7Z)v^>n|R3Xe0Nfw%pxfcTX41XP&z5xw@if^yH@b;>@v6 zEGky($yD>W+?ch1X~hf~(}#Kwp37BEdSKO+C+)2-bTo8YT>SHE=367Kcyw)P4CdQ@ z+3-!i*1Xl-=Fe6Z-ue>cbgSoAZ|0klFz3^IwyyYW7V@#=d!EsDK25L3le$7ZwT>B; zmfl#o({?vU`I^54Pwus4MC|%*e%R>mnltMjU0vf5wX-THVCqT#wy&=R-aS|}<>~QI z&4U{Iv{uhOE6-bd{-8}T_o?k{2QN)`o%bZz@J?AX!->z)K3z_$wL??0^Pa>^k9!p0 zHEYdu$?11ortX>kY=g_irCveWJG&=+wTnEyed7FbxvV#LlBJ7gPdgg+QoArlJE=N5 zY+Hq)Ur&`^k$KG5p0n~(Hk7WZeDK)q>+PM>Hs;>?{A?k2R#?&dlXvIO)mY9dE?u1G zxnya-V28AH2t%mQ<+eZOH}5>Sd41R7khljG|6KbM%BuNt?zU%62)VS|J9t9Uk2@V3 zQ<5w8MOv11&zqYIa^z!=pTQDGSqsgt= z(}lYNGq-J<^u;YZ=4PKheKjXgno^^WKdF)HV;=8Bv=IHre?-Ka%W~sln ztL~>puHEt(q^@xm+=K#!c&Lz3&FgCt6nC-u1SW-Ki&<<;?6U9Y;G(ewptP z6YRKl-9gK%v0eAdpZ5Gq-JEuh=gpz8AFuCO>gOKxDmWCc^m_M(qkqM$-k-ANzw%Y* zUe2*An{?A`>R3DT&K}!6wbr|P--!)Vt8aX`cxp@OvQv+W|1&In`KbU&X`%L}ihc$ceZm8T>Kl&{GUO{pnvwG$scsC8y?hMa$tIX%hO0c z#ay+u&*!hLJ1@0uy07gEe$ReB?+tUNa%N@vZMxjntQ;g%8x*)L%=C=S>>1ND??ug7 z@G{C=?DEo}l2tc1R`TAyV7kXH`ND>u&5V8)kvFz&(p~vsO^+d{q zC%LSAOSe5#oqi)VZF8RIrC;Ieo<)ZjKhGA^e;hyG_V332S=(~I zeSMReyKKVe`^(>Z{>;~_s@F_<{`&jdUDn$!9X&dI)~khOpURhdUeaBY_jc7e;aP!? zrW)J6{j4TD)2n6u9oX#9lKl zU)@!Dfxd5wqOaLi8b8>3#?R(z1OPYwdPBd(+mnb|)rwWye}PlT{PipJjOJXxHkp*GHze9$fM-ygTh$j=WE; zX_e&_o7GWn3(NksuDd&_Cvnz-HGa8E7UydTP2IaSbL^-VoHC2N_gx8uIYhAFz+ zeyy0WZZ4~zoXO^*w~2=WTi)I25m_24^5l}AuiB;6w=Shlo>_X^X7lNrcaBd?`{!oI zrh2FI)`o@Jms@9eJz1P(uAV0}yCo$$`hC~CHP^>T;YQsHMNvXW=hIW>FMd|*&Af^NupQd?Bk&BP+ya}KEA9@ z*`t1XIp-4dq~>a`3h#G0sk16ye`(Cq&1&kjqf;3 zSn56PRETHJwMY6^V(qifShbYg;bW~l7L=E};;3tG?o#!Aoq@J%%R*P&UiCNRXAlitMhU7Z=V&ngFT-&oNV#%?r8hZ zkaHo!Q}5-On%S$1j;;>>yfm#W@8nVIJYTg*CDTO|y;fbCdgpuDgIduqGhRNAE&jY^ zU&z)Kk6u}rt-U*S&+RhZQ%`rE3M}4w=ShmDc2Qus#T=<}-HTr9+vOJvnTf4nSY=dy zq@J_R`?GfNyroH+#kgdx?kVZvdn~g>yMVoE=vu$byaezr+2v6 zZ)TQhpQ5HM7ihN7FqifUOKH`(?b4sT_@_{7QSn3WeH^!n<96?4-~VHu?d)F7tMzA^ zJvL8yxTt8JpBrzidVN5f&8^iB*Y2*EY3O^bcWLRx?IKSmP5$wjzk2!xMW^T)?3xB| zW*u30Cc3bgH@UpoDYi+#Jzlc-WzpZZ-vv*0UYS?3STjdu*V5%Zzg0g^yu`EO*p8jo zo?F>Y-u7VijaAEwli!xye2`w&|1bF$>+e@q{~5HNB_|giwM>293QuDeC{3RwoN&*({Q0??wsgX z?MsYVFXyaDaDHFS=J8g3LPFGgjqEvl<-P}O+5770p>J(c;~8&~p+e)RrJPkcM&lvPaE#d*Kh%hqnIUXfq6M3XZ-k!|U+ z+lyXGTD@DI?R8{|^2GA4zT6c*H%z%36Eg3`u0Y!@pJr|k-qpFNWbwq$JAcnun;LiV zgY~>m5*N2ypWlCCYcC(i-Q8(?llO}2X-`}tGHbP$zRrJ!zx@6t_BHPeYNK-VABCjP zUlo`9Z$9_acYj;1tpB(0ujs$m&-!28kpD5^Kf{Zv%7wq57dF4ToLw)p@Ym93_Afu| zi+g{={Xav%zpnobrJvou=J5aVsQ+SXbK&pj9S6T=Zr$G*@HgnQ{Ffi``sHtw|1+$p zcdg&`bMdcj?Eh2>B*Pox4LkckiH!)RrNAHQt z6KBLM-6-jDQ}@_~Y$N3$@0HU--%Ty%?Ka+TeZ$Y7d-9TZ*E++~&y`QzmHxA#BtP`Z z#xHwn&1WvISNpp=u;}=~z@VL~EKja2y496yb}3i)asR$>goG)jn&jDEibHrgPPQrbERs@fjkEmd!TON}YVKFZ0W>UfG9Iza_ToeL9zK zoDy^N;LfDGzY88MJ+2yYt@n7F@Otj7t;@c8y=K^M(GA!4Jae*4 z^o(|y-suiJe>>c0L1 zTW`IJ?}m?6#qXZVU8mc*Rl6W$t=O-tYg_6s<$K?oUfMqU;mMf&{c93mi|54b&V3ej zYr~sQ0PR_9Lj-qhF=6c!M2YU}L8YO8sRejU>Z{bi+WlIv3GJymf@iBf#Vx?gJDGuOW| zxNJ}n`g5h#$*mGAmffE0<*Rqp<=d8y_p56Cp01nlDb3xcIB37EzFgVDyEl5&>)yP( z)1%BCxU=Yrs%p4pVN6%pL(9;u?uI*)AF7{_lg@n2dv#k`Si|(*!+RDTR|!|*)3U^5Poc;Rmn>p=T3A#u6^cnkSt&cW!r?cJ20^NgrnWR{ibCHa>Y`j_%{z-BXsT zEtWW@wQ0(d7gK_Kx7`)Gb>?p2=L7juXD3bCu5l}pPjP{ku0)oz$V`=LnZ-#4hc`tj zl^qva86vf6sUG9nOG%-djRhX6E}xO=6}esYrLFGu;#U%2_Vqk+1! zGoHsCn|3SXd%#}XU%{s;ub1o#IdVQk{eOmI`YD~i>kp_;zoEmPTe{41&3B(NCDrSit8$+{tjwO&nP2|q z@cPrq$Br#=bLMoKurT3at>QxUv*(xf}HTV6j3`dok6|LDW1 zKkfeKB*}m5# z#}sT>m(P3ir`VMRm)C_olbabUE_-GZ&oVKE)0ILG4sV$)wU2eplR53m$0zN3s-Sh zS>L-f_DaadAL-gRzU6o;?$#=5iFwMgRpMMgOi0fiv#lG=Icvl!y^q}setGxdoaejP zt_xq^$=bU1YMuI&H=Z|nR($$+dR5!SaG}Sc7sK{^O1ZJnOFebNBu9-CXHHBidahgY zIOgKkGqwI&?^M=APR?};mzkTay3sl?`hK?ioWBKMuD(nU`W3q7lUZxy8L_RoZbuaJ z%HJfpor%f6wd2Rre5*H?9yWWo-I<%YX==8?Kh-($=YLK<9J~B@Z*qS7#k8G!`*utZ z&YTw!8~W(u?IUJ+iZgF6JCS2?sr#DwFl9hTMtG&F|{qoMkQQIe$`y46X@i@s~>h*gOyR&Qz zcNX1|RW-SrE<1H;tnhWy_0{LkikRHuPcN)F%e#>MvdOK#R!ddfFRz=T{PIeR#xLJ9 zx!dn2=Kr+bpJ?l}{a@l>rrt{X6yb!5ORIU%vKx)jyY=&Hv9}V1Fae{P*GR zA9jfo=GQF#GkwS7Zx=%Uc5m~TST*-2le~Ytp|SU0*8SNt)kH1{<-U330&TwH23G#t5@9&qo1lxUJ^cW*Op1SOA>6oUxjOz%%5UZ zd3pA_b5e2EJt4X)PJMm7`6joSqNbzYn-{nI3cGqzEl(NrEZO9_&4z!c<`UC$@ms&; zy?wOKu<5~-OZi69Gb@+fj5HLoRMkD+ExT4@rSO|Ksup)DCPsSQpQ?K8R<2gmrO9VD z?@C>pd1JcqW}||{-YmVKn(}gergvGl?@YRN+cVd6`P98uR*C0o4tJiqvvbksFt_b< ztIXC$NBpWth>ehqIXoy zl6j?kL01cu-W_%OI(6@%wM!p&d;YqrX=!~)c!K4oKL+}Db}xGsT+(9{lFaG3OO^ZL zl!e1R zYG=cDZF@ES)Tz3S3;r`a<371Q@@3iFb7CR$+`3m5oa$N`akgu_rTN~PU)PLc=at>v zV&wh4wd8SSyU+&T%aR=0w%l8@WacUco;<3~dn>NLCC1JBN$ifj|Dx8^rlr;1>YR6S z;))BI=YP#y{`>v(IInL%zKiVjtXy05&TiuhyXKgOdapqmTJc8OsVzWdeiS@$$6je{krZ-S<{Qwc|HFWK2ttu;-k11AB8VZ zpC@isy6;)wB@Lny0#3R$mu8K6~GdA6HUs4t%?C z=U~Cx{|wTx*Zj*OWz4pGGnM&r_iStRMk|vm);Y$P*G!u7ZPIo%jXS2Zrez)YT9+JV zdGFfNqn~G2+UVyU&3_S8ZDUw8ef5^xd2>smo~?bcc}0=a*4SWk*YZVEYF?&o+q~xB z%9@h=LsysIno<_jH9PLds*LHC{0IJi?YQb%R2JBqteYF7Dd@{J<(=!ArN%Rqc$A_p zobG>Oa&@yd$CR|#na46;Eb^SP`)pPDfN4nOYd*5aEO0Fnwt>&wF#Z~sj%YQJh z{-tgZJE=|TbeO(*ly)TN%{g<;F5Q>f`LyZHDiiL+C;LwOFF0}Obw;6DTwa%_{7I?V z8aqxNeed&^H*&Z1wd5+hYk~gP&1QYy6*Yg?;wO1urd<+!+qzAA<%_s?RTk&B3fw(o zbZxJ~iQgVm-`-XHCX{;N=BsskgD>U26l2v*%PM+1CD;8*OwrdL3t!7c{b!hQ+^q1G z&CV0sX2)Kg{quWhncX*Uxx>rbY_IuU-`~|WrL^Tn&WPCE?_`v%?J|7F>$ z{|pxc_3ky=%)Og2)8=%U(c`mcZ-zeG9kH>X*>84TbbevitJoZ-naS8T@DX6Ri2~r>U1eS4n-Umw)z*UmvyJ%$y!F zZ_fR;OuKnS#`*@6e@&VE^u_getzUPyb^PM@3Vf}5PwZ+*cX#^z3b#dvqt53yFc<%4 zV32=eJ>l;L$@g#kV|%+_hQBzo@7Me@w{4=o{Ac*P^TwZp-CzG~X5MGHD*Ud|*~_=W zQ?@)?YUCgMX5!q`_F$#Yf-?^8S~30cihcK9*^AuT@8IeEHL=XJ#_m7EiJLb+wrR9P z<)6H<_mKRP*dz8^>mB}f6&h}j+4{?J`QN9>uLJEZ=NA0k^11%^#Z~|0YhK^F6Mss0 z()}F2m%o*sBuD;S{pp$i<>jAqz8hcs&+sj9(ZAfIzm9HdlXz)7Tfh2F{MC4!`siEL z{~3gC97=u3e)03)qw~`qKFj~v|IhoW{ytOr8HY92*R1@#=ELN#Yxn+V_-GrU{cH0V zo_N*sX>5xB;^t4?dcM5#v!xr;*JHcSl-;ZB%(ks{*H<}fTzx{~owY9~+w!!C8#dD> zM`5+**<*r!L7wmT9_};Dmgkq# z`#o#R{-s^3tXAuG?*7(0b7MHuv1g`*=T7LQ`PS-csp)oKbN!N`S#o+;tNflw>2o$q z-`eZGh?ui>@tRPLk4v7HJ-&HVE4NPLXik5l?(XRHhP^qj_8XMGUukaoJ?qu6Igfjn zKXToX`S+){^^DOcK4R|Ms3P%RhxZO z?n{SivhY6dFl(k?T)VeU^PMG=VtIF0?5ZCeNt)sn?RTo=p66v$&2#Qu{%cVen z*W2Pb11qv$+An(VnET1DW8HJtI@=S0^HQ(8-kRHIqOz^LY}Kpl+h#qfzmcF{pEe7kMEOxSreU=Ty35C{!Uc) z9o075u5hC(mZA}BrfY4i=~h0wsdA#y>~9rwGWPyjwCGawjd-ej;mqP3LK;)!*ja z`;&AzGEe2nsV^tp7SEoY{ibi%j=#EMnRC56PUia7sP)^K>hPt! zd~P0l;6%B>&gC9wmy5`%X8oOc&A4THlc$$ZwAA7|=l1;b%sO zmff?hy)`wu;m7KlcbT8xzh>R+Oqqv;8qc0SjGp>rn`*6l+o!JE9^Y5h*dJXQbSG5C z{9pK-u-_>rH>E zIg6h4eCy9_)Omi(e12ia%Xh95J~rvQ$_m*T<(P&10(Ib*b=;k-ZzvU;b!n zQ(yJmG3NNAo;h*P9=%%q=)=0JA$8T3?sF=;Czkdf|Fd?ojc;tk?kw}LAkzD+L@cG zcLd}-(RybiYW7NV^&|O`gF^BXdA)L`_GRz6-MG-cDza(L7B1-V=?OGDSGxo^EFJk953_|%NKqFx#ICX}?@X>vNL zv-!4iKKtLy6sPa!bsz5ztrnV9^5*l7S?gOry#1h@CBMM0f8w(1!usKD3lGZA^NYB& z{pjY-?^;LH4}JS^eBYX^)tZ6V)8kk^FOK>A*hf3#Vac``VXlrj6~~TU`+jNW-2B#* zlO0E%-ORdVQ<0Nm@pg?SXVt!w+a!FZs2mSXgq*K-%!-qte~aet27xcqQR+>g_> zHkoTze)};iGvj!;mFJnevd{WI+&5fmacBFz8+%2r=tt!q@9N#odCc=z+3h^NUQ4Hd zIa|+tKOj@F@>)&%wcod;*Z6Rqa|nnzaGFb-BTq6Xw<_f0z0DQ3oTQWD{JUcg+nxNa zet+$RkF%yN=W&q@xv2X%J?dJH+r$%o=9;_vOcsASrGNEd{-G-C7tB{>&gM@o-j%&N z_VLaoe&&T1b5q|}zsn1|T7Ky2jg#vqhA(E_=qe!~sQ6;$miLYMOomUA-WI!O1svVB zciV=!v5T_SCVoGYeD%v^`E@&QoJjKG-Fo*%v|E{M^xjDpJ#MAnt}OM)^!1Z9e46cb z{71i)R(Q&-4YR(jw6dOEXSr*|z9X~aZU&z^@3(GWmhjd2t~5J>nt6LoM)D8-sdr#F)KPvq2h1CT?3uf;y|K}bQZt`o_q^q+k zLPS1=yf*#*^Pk6xX!!OlZH~4LnptMT*tAd+J{8UTJmt;L)RAKYr9Y7a8tl`VuqOY{Thm_7WDgf~O^;A1^yu zu6JCX|6}#5{|qwc_qWS;^DTeAOFiN8$Ie^D^~qo5?nJ-#o^6(=^~r0-vCC%WzdozE zWUyDVF~3)M>O4`( z+J5)E;ulA6q-AN}kJ()HoOREPgU8#dqqbLxPG`C0dvv9j(Bj2GpF>POo_-LgJ2^W$ zF7cQv0s_nPc4qBfZ`^%0b>2g3dGY#-n{2psju%^r zpY>a)d;783MZN4PTaC8eeYaOj%GzbB;H;|YfvgXo#vVMqb!Kwv>u_a@pGlYFdF9p@ zt2H;Cwb?!QbkwXzmrHr9Z(I*M^}YPm? znLo|A@A)e$+18f zcHb4>_N{K}$=>67U&AUVCtO-;HAOY7@Wq-ZuB&%DMF&j?e7`E_$hBSa-_v7=u8+mGBa`ASV*|l3I$KG0J683WIG)=x79pBrUOI)VC zl33bq^^q&;im!3jV!M;yCPm9%&egMu*ZuwLuX4HS?%rKF+hr%L{t&b|?#)N@%f<8a zi{D4fYh?yaeZ$Lj$6)Ot8()9D*TE+XyMO%q6!vkc%c_r8wN~7}{&@azrn@mF)3;|$?&Ya5g%`i>SYy39*ZSIzjY}@o-QK%) z$*mf#FWd8@0w+$KC@CLgq^$haK$+%JSYe01mB>8-8lFSh5oai3P3^vzq{ z==F`5wWnwPIlE$4LgtV1O?kQAXEx@At4yvI&eGNTHv4nY>a@pmxF4vUI=FP6}EoDb>?)CU;)yX)CdI^|Y>gwyhGn zw}S$kzP_ld*qO~8`099Q&h0BP>(X9Zt$F|CWSD>3)06Qh=eJ&xeX?!e*=GywIAnUJ zAJ2<v3>E@H$B=hQSUl^ zoeuSseGgmocygBQD!XsRI~I!kSe|w#Xszb9_O5tY`6t&ow%ZB&vj%^T37ab&9A|VY z$}<0%)Qh?M(pGWqiM^GUD$;vyx#P?Q-M58bEtwvyxqPvS9W6gaU#wl}Rx9LrLvBjuzV8>; zX9dUGoY?L1pP@59=|6+-`D^a|_Ro0Fy^nmAHut2(=X_z;%U9mH7e!k3#!NeREBUbG zvhRf_Rkm$!y)T#hYwyiRyu!g({bPEZdn8oTbUyQHx(inROSuwr+Hl(CmUXA@K zEk5}ow*9r(eEYz@uhzlg#d}|hKfBq~|7q!S?}r8Jwbc3@?Of;0Keg$Z^~b{9D^t8a z-`;n&?QNuQ^}b)(hb5OKmAJ4kKEP;M!F*2Kf^~b*-*ea2gxxRt9vYXuBX|KzjHTvf40By@A1XIl&b3{-hR)$`;ET#lYdDun^&DSz4d0%&)9n1%bIok`{^BTA3wSIZin@68JoBt zj~+joYN2`H$76fZ*1I+Vt4oZJ)Zr`U~8_NdeNQh+h)FTI?=B_zmzpFSmx_C z_P6m{p9Xlp{r2Sh-uHf|tZhT>i>{kSJ+q#^Fz?c~w;FpVKVC0d-z}B*ah~JX!wV$u ziXOSVY18NBGi9BgzEIVPul`W(b^HGPdym#v?wYvNI(ze*j~*X`CO+Jpyf{1Q_XrF3>O{m|7SS-D*yMH)&ChbK1kZX=1>04#b5LPGw9sVzvCSB@AuyS3}yS@ z9@}+oX3yE)S#Q?%TTOjadbe}q=a@Gu;%Ak4FY}9?7;%4UV8klb`fZ%+za8HB{;zD` z_l>vSUA(?2VBPM?JZHV$h3--J{Szo@FdRLieDZKpE3|H!LnSZN( z?Cb0qC#(AAtyr>nPjm5`zzMc^Eti?jB#7uV&_THlKdewcq2TUA0=HGfOVGGs5n0 z??&5cHw*QYH>%mBi?nEYUa;)7vFh0QTu1ajXzbnuX}g*o!zB9 zW76!>`Nd+(`=d^oO+8)u?(OZK(z60DUs^CQ$azHFj*7|ka_$HUYhCCzbz!DaP)MuF zNzWjkT@$BI=@HJnnr|^B*kL*c%0xwxN7R{bsJn^CFg`%&D zc{&&8JdWLyZ|v3i==}u0C#6BMj`~~DE_c{nI&m`V_Ppq;Vw)E}&N5wl@>WdUJG-3! z443?`*jw4=KC%3AdDX7_Y9DqyN_y`5_}c8kb(g*w-`x1;S%KetnOTdny502!Y&rIs zd^WGon(^T;w{*Vg^w|%57xFGLOsMAR);s?2kazm!i-nK26s=x4$$wA7eue9H<(Kx0 zd=~v}7_!;2@c6;Qwp-%dBYw4Sxz`=@pW)Y`7w0CgjNf^D<)fPsJ4JI(?>+wW_LSMT zqbCOLzWw$h-!YXO&u!*=-#p&G^Ka(XpQhz;zBhBU+rBR2SABNVyj$SN-K^u9b2+ap z^;;(TF5ZzA$O2-(R&$)^oIfUA_AJ-t=<*Lf%c;M>XHnS;+a+&xjAS zm-y^9`Ld4m)MJ-(_MDlq?B)BOmd9yE(R0=&FSpn-fA&o-u{ZDKrEgdJ4K-)SOcrg~Gp+fS<=pAXtM6{J__M3+?ki6--H_TXg5SEWp7rY8 z+k5)^#vBjFT?zpWK$8(laWozHIL_E$`6Z z75Cb1cxkQ=`M!|5Fe7)1W}dF6lR(R?XWUA>E_(e%ybH+opyJDZ;1@$C7 zRhtsDI={$0(Olk8CcU{v@UZr#!%6EFyJo!Rd7Zq^(?vz;5vy$9fg7LptE-*mD*NT~ zRC(`<{aT)J@d~$dh57F5e|kG7dy3aFdy(HZuPpD>>pZC|+qL-2&b{%k?N+XRSQxu= zb=Kc&TI+@1?RebZDLZwo&aq23Hy_;a`t00!DYx5gZ_ZwCT>Dbe?dEx-Ij`>AoAhN< zTAX*W(Zd&OtUjv7b@CSdo)+Zk6>v&u%2G`wrE7JGXXlkIw7&f9)8xsYPF{bQXM5(w zb?wKW)~h|roN+%p;`sKod#A$07A%-+uC7&R&z-yN=8csH|MK^KT--IQJTlm|mG{>l zwb=@1-bCi=RF}?4F`bzE>G#dN9chnSzRrG~ySUWLEOFCmm)f>VuA5`FF1>kii%pHR zr>1sRaJ{Of%5(Ks?>?wZPkdM&{quX^tN2jUd70D-Z9nB)dp*(ar+2RXMx&Vg_FKZykqH&w9JN-hPRLU)x--cwKbo=Vv1K*4 z#NkgmoDxT(!%uEkQhY7VUMniQJg8mn-Qowowg>)wTNU;p_x8KRTAF_AJN2$g?6|ad zcHG(Dv0_)ge7>;w6S<7#hv1#j+Jdpk^B%ak*2H}~DgU90Bm9xUADx>Zi| zL)Gs4s-MahrR1+)FIwssAF|tSa>}cicKz$q3^nIuS=S~VIQ^!qr&`(~UwC~9O6A zqF2`XoV7aT5?ft22^Zehx4yO7@X_r@n^#x9T(jf&`Yky;-^F6{N~b@s&KEkL$+hUQ z%#Ni?+j|pzOpVs19y6S9{kF@A_p+HkHiv!oHM?JTrd-zSRQKGU^L?*x=??p-btUb& zs;axB>S5Wl%i}cjs;z@oPOj4VqCR(*@0DC<;~CXE?(FQ(mpgkp=uzwI;Q7^ctGvV& z9%_dL2Q>xdemLc&Yr|P{p;iX{~27_>;E(GuiXFMX#KzAjptw6OTWwb8~vZ*v|W86Z}#tZ-~ZnF z&#+y%zww^o?9cOlr>?DE|HuAnSK^oBVtFTSUH{WR>7QX^kln+j+QGbqpN!sb$`&zu z7#?c!+0VO>ExXL7#8!3nv(~fUzy6t?E-Y9cxLoGfko8afqi}xu zZ>tN7r#W|~M15vI{@CJXY}(9ciT%;x7n8TFS@Ub{y|y@#rz_Z9$nA^;3TcF8-uCXA z^RzVN>-nCGOHMpwQE=uW^FI{{U+>X==9|0+ca*+*gkoD=Tzv;yeHq6UB171vQ%34)X9q^PX~Ng;E)fvMZGL{<>l^PXPtDbWo6Nw?WV{1w2Pw6 zJeQRo^gku+vg!CHshMgwPp;axWY*uH?PyZ%!&nU6+E%w{7?!nW?w|D1dZJDAj zs&O)`KP&UC=i6H~i{|W+HmbfmS0!lu_vcDE3r&{2(tc^mr)rw1E)tv@o33|`bKUhx z))x~GF80*fwbJ)=RMk@3MImP|*_wAf3tMlxNy}`(pZxRl4+VU^`ON=k*XP|^TNjkP zmdf>d^X`?T;GG=_^XznMG*3lFo2zr|T{mU(y`S3kmQ!-?S$s6=tLo#E4ou#?`NtVe zw@dFfxu+)GFv-;R>X+OfD{+(x8@@B~&vnO532izSW@3xr-n&gZpF4#IH`(nzo8{c5m6Fth}fhm2BP_JxOzQFTG9Ao^HH; zsm7z*kGksB%{@+E@e28PaqY2#ziNu#Ej)Es`|wj=z2dFKac9cbd+AL*dc0}w?Kt^) zF>_nBtgN#NriRQizWJ#CQF?bp{_7a6`*)0Y+h^rwd#{>X*j!x{@i%J5af!9%Dknc# zmp!=Uowleme8PkoZ`^}FFY$EtjOKW+VrpD0ZV-9FaB)%Kq4&oEKs@@g~B_a|#b zF8}1{U3yh!MeJ>!ymz}-T3y<`W5>&=vJXFA-pIQ<-(fAYGTXLKrAueLz1(}}Xu#u# z-|k-DyJLsNwh!f7m(RWze$I$_LG35EWqa#v%{5Kr@|UIV*r_X*yZ#M#Mk0sWO-mlH zovSQFEaxiwYpTm{n1zR&GPj@8!vNV%KI|Q9W98X2qUY=gP`LRc^&ZUC-R-@?>JArt@i^ zbqgb9pTxO4JW-uyRCaou@0K6cnsa;Y8pZpr-?U^D6S^H!G;PHu-bL{hxjLse6da3N zE>oA%wqe0ii&VEii=zrn3uCgo`ljBEvgeh2xnKX2?cL->Gh@vwjW+jAUKo4nZCu%p z->Zv{OjF4#k12Tc?sCw(9}kcE$yr=mouQ}vA%FQx}u-vd3QG234BaWE{e4J^Q~zEXM|7CA#SbjV&&{J(`Q^0^3v(Qb1(V@d+fXJOI20O zcfS|k{py~-$1icsD{qg>d0Bk2kG3CvcD-#+P~d%cudv;{xi9Cd*exzzC$FwEzshE7 z`SZQ^s}J9e+*$pi_USr3z4z_6SB34)bYEXt{lfdPlF^6Xvr&^~%}+m=Pk?#!rQ ztk<(?{=sm*KDFvY%&*X2kVT4TT+>wIn4#|}-zAfBcS&FU5mlha&uLOU`Lo(W+dXB) zqThd;ojIl5d32$n%)OP@r*6LeRyFj-qqxG8>y~D@tQL=r)b!adzj5=PiKi|;-D}>h z)E~UxGp5?+KI@i0f^Ta#<-YTpCsY+&`tCnN^v;J-mt-EFxD`EjjYj3^eYuyO#XS1% z_f=@S*yPq3hjcYhJ)X94p0S+rH<<@_y&hkia&n2h_?NqnSI*53`Q3Zqb~V?YyX}e7 zEI!vxn%njGbnt~2m)w@62=29Bb?Hx0GVAkmS$!e*#7tJV=Kh*I>+N^LyK=|3zFoEK zMwRLpwtvmNa%tCfEsD)jx7@AWrf0I^>e5fqs9jj2M~0gH`ACmH2V z5fZ*~=KMnOIcI*&oY_%STs(Qs_RW{p#8g+z_;7sdraM9&ccl-uH%feKyYxk-=lqJK zGk1S?P3*p9X=d;{=M{%tvR|~>R9>qb6PU~9se2n+?GCcD>Qb?`XhVx=f13oBA-re z%eP&4u=Me!TUo)Tm9lR#%r?K>)aq2pw`!ZTrb~%M?4M<^$_ejn4xW>!(mmdrXZtSb z+o>%*HNJ0eKk`0xQL?1@;`W(Vb31wSjXq!gS@$^W%I@t&Uw@g+S$lg;(6%MB-mB}b zt=u?ibwtat+E47(v-c*I1s{(yz4OTB$<+Oxj?5WJH>KBT@>NOard+Gm)?1r+O)SG) zY*jnwCC^Dg_cMAeT&kAViPu%<%Z2?B4EBFIXUYDHW{dtaEU=q6u`$+KKYZ(~OH1B) zOFhy4c;BjbcJS?O-Frjc7A?Cw#e)5j?y}1N49&M=5@bT@og$eVS~A1}Ul)Bcx5skd=x&rabPdb?+e=@ur%OkOj0 z$D66`>f2&(Em?bQt#g;@!upL*PKDM5yBAn$FDVz2^$slX?e4vG zSL)KE&NGQGPpmtw{LjU!V5+v{Y~4jBlX}%Q*`3-|V!f|%g{^kfu`A|oS6}R`p7zrI zOSJO3b)RyonlH;1Pu>x;K0R~$!c47NIm7pc_wLTtKXf}gHE3zr=eoX_moIAi9_{;j z`Rw;%@lB?2!J&QoPMT_Nd9r$C*=g?03A<(oaf$f~iA*U86#BjC#iCbUy6dhQDzA7s z{lmShWmhJ>y>;z4X#Vc;iI}7HlYTz;eikw(y>Uh4PLo&jdDM2_u)BJcPh&r8xola~ zev#d`B-5B1AD=1vxCxX+IMp8!wc7sSL`agke+DD8CdelIdJX6yZcu- zGu`_6_4F#vil?fgA8!8AJ@YKq-s36vpWCzb|An_NzjJEGwfl2#Zq?qLz47P9T>|(Og+GrgR_aa>U7PY~j$T(` zo}kbi-L+@uu4SM8WX+rH{e27hwY&sAzF+=f{`|hMqgSeSUgI{t@?+hzY^gn;ipuTH z%2g*%`mS-&(XXpB&&zsI>5B(bTO<3N9G5TdUw%pEQ;D&;&C=^1+E%_YufDU=?&yZU zOhsk$f9!YKDz>sHyYtubr1W;T7yT#J3BBlfbnx1nwfyYqEN5;_InB0wlZ{~h!|<-T zqS}@7&R*ZjYN~0k^!T(>ujbn_?@UYgps4+NNl#B+dtS28zLei6^wZXOW%s7Ii7}J) z=6+or|Ma!tyARh&HW|LSE84SUQ?ACg$v^MpmFFL{@C?1Q-t}I{OV^M4Cr3YbDR(;} zo4ZM0yYSkSe5uo%JIsn|SdY$W4n7tABlHeC&y}U6rjnDU#06jH%f4JM^7^JkX3(8H zbNOO@EBhB)suN%QJ@=nsjohS+J*SKOBBvKTmR@@~_qFe>J>PPE>8StVF5LCJW!#`I`8hj%4?T-)$Y|>{(hzY{)l={No3PCCjwUQuUob=^I`4O35=zRULb&#-9XYxl+ZYX2FUf0Z{bi|Ouj z?XeD+RZ_C+@$vfd&{)@bLx4+e}+%r*8Jlvvwv3d=;NjLm!^K-d;Z6g{Fg6( zv&?_qWB;E)(|~fvA@v#?R4>H*Yk%y^t!Qa zm3zRz_WU1%{fTnL-wDR|Z_L{*Eq-bJ#hJBV?PqSQ*#7cA!|&paKbys0|4d`IDX#lD zuka7=tN1@g2NM2<{MjtO)cw)y&=;J)jdp+k=l;i8BuU5o#hbW?{Gaq5$=}+)pzf%} zjN9vwjwC#{qwZGae)y4u`cFAe=EII8{B!kZvHHu)KetuSeDR;*x5mYKvqyiEZaVXn z%se}N?>qfh>!;bTeQW!lp))7Z=u7j9pZ^}6-(2w7{Lk_K462`|-t>?Rs{G79>`iK6HiscRof;$bjX`Ypy zoq54vkKLqGno40?u9x*L3s%_{=V{BYdsbO@`?5(V=CyOo>b|&L+>m=`$?GZmMIPy< zTiO3P_Q&SRwcBO)?gs_mczSE9^2U$hJYOz;>8N#j##wTC%KPw1U)HqWztgf>=FSc~ zu~*x?j#)qIT@_}oXL)zcqBT1n?LOXoWbNDAGd&-)%WW!q^18?Cs``nTZIc{r#r4mu zd}i_{x0vgYRqyhRZ8_d^ZullHD?Cxs;@zsX{F#D+ zB}z(4p9RI8^baY1DqAvX(Ve;m^Z6fqr`q|q$IW#)?t3jMMLI|~Q!_hqE5o+5p#Kb| zZ>}tI3O4=TGbxs%Dt%P7Zk}q)sZ145r!qOa=T1sWZ+EUQ_^KPree$Z8v9Wzq zn&Q7jOaH0v`<=BfNqvc2>vvVXj?94kvtjS*1S_`fL0-!z@m%lUgoM98(_c?iJGZ&+ z_|Nr!Z(aG%z_c%a?-}3q6EfCme6lV|@^!1$x)v3j@@&Vcv?bGdEm_$#Gwo*n%#{)g zQg+*zx?Lvwse24>{29BWUyX{tz|5t^=_TyzbWwj*Sr%mRoV9Kon*SR-z3G&E&cMz%L~7r{k1UH-=SsR*V>vR z{}~#t&Ug58d&b@PlUv0-&U?8E|&?0vbuS8;yQnz=$D-dz$iH!Qt+u{vAn!tUvj?|MA1OPwfP7au(NklM7isb(hb zyLWem$3*8%N=y-1@6o8TTm4qQYU&5q;~rPmzm)2o>n^+R)0HbLAI8hdt4t04Xw3ae zT_rQt?a5m2$)+7gAEiG!cWH9yyJuH(V?#1OM(tQwWLx)os7M z8z+6JxNL7Ju~TZg_Umt-)_C5!`sK}uQ+m3~xv%M|&6;#;PC9RIg;w8|PSY@pzrE*- z3vYaTF1lyu(YGfzt17HDe649}e3H#=k;#-9T`y|=SMu6jDNJpjT`m@POE~*liM_|3 znd@#niwi$^J*SpiTk7u7hf5wTS^apix%CI>`^Pu`=ucmno2K}uzyD{=Dlw^;EhlEI z5jPImGhbxd@2od_4*TAodxR}0Wr?tWfM@uG3%w05>sG&i%lP2zwQs7&Wq((wM>_u4 z_R+OsQN@3Tmya+1XZWx_{NdS(35Ay}n}y702QCQrT;W{(GVY7tXRPen>IRdo5VKrMA#CyL89j)tlmTc26!l&bx8RyUtpf!od0V zDV}!cWUuCG@3@llYTNahRWe35KVDdL>rY|f@`C?As%0jaS5X|1N#?*RW5xHG7k>mrkrvv{(4s6wc4m z+TGiOHYX_a`)dOzp)GSLe?zXXrX@|#~CdSy-Kwc5R}C-&BG?%njyYjvGUK7T;v zarWhZCa(ItY_WgU%N^nQ1-pKJ?ujqW4BqzkICtScldDnDz9;r1ZsW5(dY)T%Z^xs| zR*!R@t&d`|u3uTMC~)a*UeXGS-nlL!+onjr4}Z;3c8PO|`pxZvu^tm|8QnaWx;xwJ z>FRAcOC`z+x3ca|UXrnA!s@U5oWJB7SA1n&XJIvMspj3&mDfvox~CN7J&{?z^3?7r zpJI1h-P@O4y{PDX>upw1cHTJUrN-S&Q}&2@w`D(_?527(F5>*l{h_6~Otw#F z|FD{}E+V(!Wb~Y=e=^tJe(QJVa z?>KTlNY`NY*Ke~kdc$}0{0O>wQ}3kaWhLX6SF+g+Uj|*=I{R16>cgD-#26glneAfJt_oI)T(doy6ma+L!t5{8p5M1lI-b5MW!GW5>rQW0tG%2z^ZmQj zKA-953Ug0A{!nOdqY81QzF_}ZeB??(2+7W7v=h_ zvAZrfmV0@v>rd%12M z+0Ik#d;P`q`JyM?H*MI;CmFu!+k3VRSKrx(?eKS~{ZW*CUG_!MBC98rhAFN=GD$A! zH-lsozr{Lga(hmiv`AT5S#jN*qHyaI+m=Ty+rIpv`xd#c8+H6EV_VXD-hS{h@;nxp z)o0|la@nd`)lxUk9m(VVq&hG7V{|}XxP0QBrq9w@t1Ysu#4ag4-*LKo;pu>BGD^`r zUfUa=tj{J4e&1%+C(AjKO!ku>14;b+XnwFP&TY zT4uVZj!e#SowQqd5!)y2?zNa&05?*hh6!o|vJP&#EA0C;mNa;+;&sg{&=o-mCo4-r)XbR-fwr zJ?+hH(HVO@uh}hp%f4uTV12}2=}TW_X3u@LzBB*cOjFQ$h9wqD?){9}ez0=Ot)I`6 zR|VYDi8Nkvyp_xPvzkxsjEzdJsV9X}JtsVhO0p@F^1565{cfZxho{t(#Y#%XhMjIk zQ>G-%o?POYpMRjYV{g?p+m-SAH4Yt7daCKURN|=CPS0H`lR|UfcxPJ+Y_+rPD(G*m zSR3c}C+bgezm#V9%rj4PJ2u{(-SybWsOHl}uJFVude=T0r-%m|2i^NLtLN9eyDuxY zZINX=XYyk0anN#E}v873aL2n zXLd{Pl&9`e6&Dv5*EKdr=i0n=DqsF~sk-{p$?Ff<-hOdi>(Qt6>PatetbQ(L`1WS= zQf=;l0N?dIE5r9^=dJnO+A{3`1H(<@dr#|L)GYp+mTmv1SXWrwqwdzZU$Ohs(r4{| zX8n_SYTci6zwTKctA4uwE_YM>_i*L?$4okQeNBEF6R&zc?Pf#$^z~2wmY;5YlP2|; zL*eU^-FND)_vW5`zw~QNi_MPR^4ZyM%&!lcUV=Zs?0x>CV#~)X}(60*!Htwb3)&md!9bon(1dP zrWez+_{M9ECDXG@-dgjPKRdJX@qW3oZ@0f#thw@Xt>>&~7qxCBYIJS;Fem8HwQaLZ z!shf$a#@~ztLL(3a7x;iCB1e!kA8ZK6&`z?me?P)z6l7 zo>TCwvWeias*Yn-_wPNSZKa-~+J?XT*NzZP+wZ+DVQ%dS@ znC;jZ%3?0`P*QWTf2Nw3lHsOZ-$YDhrPcW|?W~QK)_H0j{WZyRp6r+DGv9y84>qw& zIX-8>$F16WdAiHq`eogH@#f9cmFINxGPis)UUz9)8+Kd^7#Pgvf8S&M+z!zL>GESjxopOLRciTjdbjt87bVU-OgdWJ}4OQ{}ztZ?WlfA;SDeJ|?Y+RJ-(UOE@L;#q`Ww8+E5 zS)Y31e)<`0)}5W1=etU4ZN`h0x|_0h)#SBtUn`%-}2{+=dn4k>gn|(Q;)whzq2vc=ALVb_ujQu6JKml z6@KBIcBSWdcHFj%H%smYZ|0cf6*)b2{nn)WI-d@0nt9JG7#T=V}7TKNy>&zE@{ zZoba8Y})E+>+FwL-J0Ahvou?LPe`x!)`%lb(Jv449f^9+e0STms~_%PG`Kfe_VwFf zsrNxCJVqaP?8rKrb8ljg$>;2-yNSmQqO+FH@?R*q;;f3t3s?V>RXwJ>On0TqbxwsX zeNHiJW=COR@vJ0sNCQ#^T*z6%S<*YR@t9id^C*s~NuT3ut(^aonxbwYB zjj6gSsB&fTEEl(VdlxN!a@P3cdZR~=w?+r2-C>CGosdYxbO;ADHK*dkArp5NjuaejNfYM%ExCcG%xHdilhQvav$ zx%xrft-lwaSnnF=JKeN2=veBB!lcY;OKY!fDNp^pT08x?rtjU}`bBllK3N?e>(;jN zvR;^xxJCYOc*<_Kdj1vHxUb9J-ZcAd`W~q?m4X7@q~j$D(^va0PdP95O-^&)j_0ek z@!9K&Rh&C~twbm+SC@Ck;Y}-NuiL+T>WlMxW^60jw$`&%bBo2|ByVo_jI*|G_cJZ0 zmD*|@OPwAao^$lqt6qDlQr|L@i$4#(lGlAz(|?6GdfHYC>#9DsrB!@e7YFWmn)f7Y za=}h^?g^88d$Gfs!C4yB%JZ^SABS})@PHmYoph!?pdliBT!0Z`9A0=yT_9(#*|2&=Wk*^7`~$m8Wjb zd3@Gh_);ts$K=+bSn|M&0=m(c`pE#o*%FRH1#wIx1K1ehs_wVwN4dPA0d| zWv@F*PmA)?MOHrdi`#adB`CJN!8YiXyV=*;hk&sXJL?3`zpiWXhmd?b6$&-hMpRkf!*dlSQt*CuNnmcJBHv&2&3@ABX^%Z?lE z?n=KH>6fo0rDEsiQt)t@jnP|a?$R5O|VWG|ox2Uu4q-RbqUv3&MEl|CCx8~*4is#2|&uJM(Zo65JnYv~kM`a} z9eebyem!BjTeWz@O@rH|pM!qCoH*au?xLjF`7i$&BC7W5MTN^275*!==bts5`S!jI zg{$9Y&J*~RP;#~8`P#_$eD~Sf*Q`G#@nuoh`=|Ofjv5zToz=`M5?v4Hg?$TL_I3Zq z^^bSH*jMmWGB>o+<*=ow&#~Lf3#S;|G5f4se#>`uRLiz$cS4@$KiXQV>g73UYQ^NY zD`OY=1Ydcpd{65A{@hg&r~1Xadvj)eQ~7)Gh5CZ^f%Qk{YW!#5PK$YT)kOQ;QR%$6 z?{m}pb$4q_o;zd7H*M=lOA8g0lgoL3?$O$^yGpq6m)EjWR@v)5%hdkeJ8h@xq^4Kt zIjYr*8p1qEbuEsp>Tx{v!oPgqS$2Vwu^hWqxBTf{_D}n1Pj*nq?jqSYyUp%=o?4S- zV_5n@j=3uI^XBLGcE6r^P5oX%;u9Tiks9x2-g2XY%q@?Le(f!@v6in4jk@~c^{Knh z)`b>ZPl{=CY4u)w+Kji?kMHFPOWo?GBeRnQ4^2DtTQie$a`#1-+RU8YvaeU$tui*1 zkvz9+{$tsww>Nej3NHC>_&NFY()f_C#TTn5+8?a7J#b(zpUmorjqkN~#MhSY-ah%m zjyrP}hHvZ4yOVbN-lw-b#ZvcgFAuxzn*=haLL5x=j&% zcDGg;{gRfl6cUs@_crMDRMWN@Th|t^)La=D_9gvxP}uD*r%8*iy}4^|?YrmW=eIGN zE6e+zA-Rxd}hWndr@4pX^{_)R9(Z8?ppF#DW#osNW^+|sxsrW7X z=g@xn`Wc_g{Eqdvp1D8U{=?5OcMrdfP4Lat&yGDSv-x=6?5n}r3-b0a6- z_s`#cb$fE)q>jlq&C^x-*Qj?(zg(^v=swewMc?p)z*y~T1drS(@f&ABGhVx9bE_lc5IYQ45AUq(yn+Z5{<6h2+{=w$h& zc$@6UN&n1`YL@*_em6OwebIX7O_u8;HlLUJX0vqZ&7J?2=6R>SzBujnTl?Is)4uMT z%BTH`nv%Mj&A9*A@zt|BPI=r56k1|^VYi<}>eEj@e+l+hoIW|v_qzGpYW~!n^)AOR zd{FMq)cg8&^4pvVG9-63-RmZIY~>A>X1(6_^>;zZ2QN?imec70Kf7uco`||{8dVs+ z<*vHEn&?uCHxWCFxcZaVESeO%_VSiDruSzXE${nNdHZ($g-4r%mu*$cp6#+avt^Oi z?L&p$+)=kOE=Cm@c?!*4pJnW;>N|1PrAa0~Pv>oVf0&)?nXAzDtSd|XMUq3NuXX-B z^YpE_aMRCjV!cN-Q(sA5TWh&J{AksiWX+Si{!E^nw|9N!wMw_Y_u}&><=nY-@K3YF z`A^bowyW-&=c|>w_MLO8+P!Dj{7Y{8imbNQ?A!Bjp{9Rk`h|`?&B0&S{Jt3({CINi z$({B*vsc&c{3Y^oy5`GUY>j;z*BraMBC>UN;H|5>!yYZ<(7JljRb_7U^2`WsP4&s` zhs!H&6~3P}OWmST^~Klnom2g`o{dajo_3h?Mwr3I+tX5&cNYdpt)8f6JKJ}TIth-n|q@UoxH4nb5ZWCsp7p_>)R$i4EHyU%-HjJ zX_i*ec8>I@;7Rw^p3Ay>cJ6-bDcj24RiE|z8g}()*)c=5-?DS2?pv|(dV0{W3EfsZ z`4o073MmSlxn=9bZ?f*0%dO;HOH~CO#Ef#DT;Ek!RFb{5vcA3W^|{pxUR5b?+x+nJ z>WypkbT`cUaP&uwVacSoA(^}9XmLAW9Q`GsE;w=T0}NpX*`|9^yBB;v&QbAuj@OK0 zeT9dH8*CO{7n$lE=ejPwo@>5dp~{)bT2FVmJ)b{&OJ`=FkY_A+_D}1;KbfZ^W(Jk+ zTC3rG_-Ou_+#mP(#aGo;*Q)H)EfSC3+3vWNl}9q&TkTNX)yUJ{YIl<3PG`N|dfT0C z)yrU}KlTs*=Cb#t{W!Fj^T{Kd`jf?V4p*EDH(TT!x-r4rI;iAI2@-8G8HKkC{7V-d~gczL)LGiI3qxSO9?yp{jF1zJ^=2u$AuG;bT7*}V$Qr?yGYre%r zoheGWP_b-#+tpMXX;$5PKMmh*idnN{lSi)0axI=KTgp83Qfxi0)V^I7eWZKwPIdJs zO^f_bYpU%%$zLU0l>T^M#hGW?8-uoXW{D|AU5z<0dCF4RDW7wrrlf{%j1BSI8nw&l zQe^6~02j|pPDjDYT`C>>9Ik7cYg*dAop&W_yL8y%ENNwt>1%S`-(by=OI z6L)%M`fvYeaqg7Y-5Bj{$w`*0l8**)UQ4|0yLxHltgvNgZ@#iSC9*hpbG@gjowj6@ z%e@<(v3>VWR&HMOL2coy)0?-SdjDDEN!{bH3sv2{{KiUW&)WSCTok+Dn}f4WrvV(Pr++MvGX%l9%b znX)ZUciC7Tv|a4h+I^3nymK@US+un8c4kg_bg6OeBiC&UKAIn1KfyB2>{sUeW2Hi+ z^Y*JOm|rIQEN#}?`1W7Mc78uktu#M#!d`JH@A053u`Me+Z{2e-SpA zmmI&Yx^;iq?RMf7dzi@`pR4!I>jzd!N@pIGuuO?t{pL~Nqz~IJMMqiv-0FFyV9_3Lt8 z{x1rDH{JPjvG~It<-o?Z?mHM5cm8K+;Qu6hqV5LI_iyU!jvf87`b%1Tt^c#!dAh&q z|J`|0mvHpgzsSb(GWYkTmwn*N`GXM==&sJ+hdmw`W)U9DoOJG1WZe};+w83c|> zbo2hQ7T@9gr+Y{HxAiaNH~OiWy`Hw;-Q+*Ro;9^u>d%eW$#0vN|DU1CH2(1Wxz*9# zKb?xA-*(=$Pl@Ttwl}SdIr*#PU+%nTRrdc*nZ%#nu|Hy4z+qXn;?(!m-M>~p?SEbE zU;ijt!e_6-FT463o8R2AIs2c%@IS-Jnx)^5E&eQ#R$OVz-4I|rH*!v~-0PkzH|H7i ztNAUSd}7UIrbW3kF3n#S=dUle;6{1D%C+;mHD`UbRuzn|l3sFX(zYuZhB~jxzF5X= zFOK-WT_*eBeT#}!mu4?sc6Q5+7nLth+}sL%~)M?<%&e+s;{ z{q3EXR^{tn$Sgl+v1`5A%hiFG*2MgLIy2NuXszf`&LDBs%(w5>t>fH!=uWJv+`c*2 z`)(eWDBiO;=q*oCnCXU(kw@7Tn)P=V^xVnZY49xkbgFm1-J(;{XZMRaYX$pncoa7I zMRMrY(~&=Bem;11Q@Z%XUpoTl&bU%KJuf9PcH7JuJM))bKDk-uT*92U$3n8sXe{;pS2L@| z^leXQ(2p0VlRhV}oj32sFSVuh({|Z+_s=dnFS_%UrB=|!W2+^ruN_HcFLkcTzdLKY z=5lZI+@Rt!uJLW(+tS*XuPge`;Pp!DM#roxTVmhYObc^aZ7ua<q;E{yVw-#Y?Yi?n)6)_61yiF!{)4m)Fs~=hg=Q@-+3GowVofZ?jp+tE&8d zxqYq+c`BE9<;oqI3WJ3_sS+nob?SJBJw38xrSzt}w<6&ccS1GwOYX7T``C%hUv|g& zKZAzM@5>YZGc3Af7cT#UzyBlq(&cyWy8ha>`Ojm8D9gX%+7Zb`J8pbiHhGzf*2{{P zxiZh>zHO+@y?@Ja!fNSiCex(8m6FcJfhLb;&h+>5lT+*C+D6osN-_ zssB_XW0%XzX5*e#o%qP|tftHR8jsuO9o~Mw`=24@)r(AR-*xwc-^^wg-+1HS^P6w# zy-fG72wHD=|9jn$Alpq}EH>XQ3HJ(FT6p`ee{R{6)vvB?eS0s+24(Thcf{hGIF!XV z@{eM_@3UO^ZR^|jH&Qp;s;&N{7Ju)%UFtlRFa6(^p1wb?&~88Tdad?9XCwbJB&UB| z9JtA@U%%x1#kAg^>hdoGzwt$sOgA>2$0UDx<9~(+-ih^(W^_!liu~pG@7vBd2QKCN z@4w+=Z7~w@6P;|$cKbQ9kIk5TW#i?$_iz7P zQmudP)0|5E@Tt4LcYB}8n*I7t%u?Injr|qtf~&#{EZMgeM_;|oSa5Q4P|TMlkIGK< z-v|-Vk;u4mcXR+ndD%Dy&n^?c{=S6BVCK5_k_Y|0L6&2-5% zULM_BuIKzMl@FGxUg3FKuk^$GCG-5ZO2_Q)m-y3LTeMar&4)p@e~-`9MC-0Q>2q^Z z$|hUIDOOS-UAnwTE3W3wDXI5cjkWma;ZPY zpUbayTm1MN{483cKFfKAsl?$_o1@b!-P6-&O*?mZy?*s~k0<*z7rVa*T=Xfv%21*+ z?NQJnvogPFo!UWDmM)p#dg08O^XIQ*i<`N+$DLgM<-1+(yth@hcjw-l_@bunW@g3w z=G$ojd-%6Mzg9o%$`0E(pS!#B_Y|FuIh%Xg)7dRtV(-NVmt($p7GIXzvZid6z2D8U zwLQIdakq^4X1?0@;hDvwvzLyA$Zmh|IIcu~+RCHV>!akqJbl9dvEDWB^^a{=w)!%w zNfu`9iTghPTv5_)i;#5g&uh(+-_%Yzf4}!!v+m`CGS4;Ewam1Pmw%gGo~qn3z4OSW zrca;9J*z^5X z?O%JZK8t8)slD@k>*F3_|GTrd{!6&)5|ys+%Iuwd;;;FUp1MNIzL!*W=he^L$hBeSOD{ZCh?=``%uqR%v-|q1&&@*;}>!?qAnAQJEch?R0FJ z_52Xm_Z$;W3oQQ~ZaPgmaMpFBRZ?rB=MTW5Cct+g7Nxhrpb<>x-Mdv)!8s_938Ns%kg z+S^84d@U_?cj@lu#Xs`+pG~fNDhq0GU1`nxw))bamv5&3`leO#Pj^j6=&m=lXC8~p z`@MFn?`HXX|6Z~r+QgkRk9pCNcfUOS{Nc6JZ0(^ z_tv}1w=9(kn)dFm=4COn&f=g+KcD&D?f)_DwdlXu^AGN_{?A~bS21n-RUJLAjbXxHBt>vz=N{-{&NvU=s3wM&*x-K?oLIa0oA5C4i;%lDgRPiS7d!|rXR;{Lzp z>QBsWUpPPEzRs7=-0DhvmX!vT<=X!FlO}K9T^#Xov)J6INu|rT2JCQq_C{Le_q}@_ zzrAO__0x^_f8%gAVcMnZXKXfk2~K|e@y6=2*~voFubvFIxal;_!>dYlSNqiKr_}5w zXUpDm{mb|IbDrL}`@2Gdx-Trf4+Xe8_k?8*O$m?Pv7zM8oyok^P9!D zUh!;FxzXV%waa6_UgwJ}!>U*7Ccp2sy0$jA=F^!SDgPO|b+3NZ%=s56@tz*X`AKLFR?ZeP^3x&!_!f`QduW!o+(!=6yXFx~=Wd zy&vHd&b_kZ-nnXJ+D>VmC;Re!*GV|bUa#xXn|SQgooC_U{3~52oXyeNzFRY>S8T4$ z+-g_r;&Y2OlvqalP2Q^~|75*YJZJD#o7rE2o?Jg08?teB5m&^l)aCH`sH`b-ug1=| zdV42xb|9zd)eR39>=NcNYyP?JWLf|9SEfy0kC(k!_B-<3x7nRCGTadx?{cVq*fyu* zwRQsymOjjllk`YESMxDZe-bb2)>UGYBERVUT(Nm>U+Po+w3!^9%ddtzPALlVpS2-- z_x`xU3iX~Z{cIVYE$8}Jr#dKUb+32!4>B@(<-V!rc<92^t$$heHXrfpIwt(;y#BH0TdS7NkMB-D zyzu&auUnBT*X63Xcyyc;6h9+yW%m27FSCED)pcy?F5J2-V!Q0AE49s2WL=Lu{}>r2 zP}GxG>NzpWdH?dMQCS5yJtN#V_I)un@94Ru@8MKCQGDa)zB`qE;t#d!U4nfpvsQjM zUA8e@UfJ{1#+l!id|c;Nt^Lfx(>pzX*1dC=H=Vc5Z2u5*ai)s9TfXg@ z&lE2OU0Sk|C3ogmBO{})ZqH&DZ&mqTus`9?nFLFBt22w=PQ4L)`bggf9o5U_l4}Cz z2dh=v#RsZiXq%h9D)p>B=dX~b(tYpsAD?{xVELNRq@I%u3=DJYq;7xy8&kP-iSfA!n^bKCRT;g7nr;!l3%5Wkc?!9L=z$aNEIv#V!=jv1dhcK_)8M>Rj)MApwO zyOS3Y8uIh?t-YJ>av!nopa0tSaPG&IiHCgN%&QaES35B4&7Qn@2M^xK);}DPTDa>( z_HFM|X$!3~RA(Na^vIXntC#DhUDD;SwTqr41Z|$X{qVcT&)xs3>~%ik*IT^x+}y9$ zZqKf&^W@~*&^{adwtBzS{*7-+wpyA-Ri3n3wrZ=>2j>gD@9WZ%BG*ol_c{C}SEhG$ z+T&i6C+0?OQ||h)b7mDxy*cfktHqj|FWk~Xvp-GuTg3MATHoiNv&{2W-8ka+_(Ne{ z)2EMis_*=x_nK5ql?#3md)J=rIp69t;fn2fy(_hO%V!3K+!xQXJ+kUQ!_#Y@73Tf) zpVW8#L6m*F#FbC3^DdsS+byj)w!-SJzRq1Eb?wc+i^ za>1z+lK3K)KK(Xn`MqYrbzR{J-JCD;wtW2By({$j=QS%ny1$&Tdike?N5gL)7ReP~ zzTf-d>MxJ|pZ_(xTfBw))Y)&7Rv$iD*e){f+u9WE7_DB{xZS#4Tj&4#Vch%rY>D08 zCr5ew?%wM@@%QXAuUx%vPqJet8tq#vbXRRw#l2#=b2Hbdb;jP3?oZV{93P~z+UQkV z|NFaAR#TTo-?()9ox024%z~f7IoI|(yj!9d{wnpK*V*}ev)A4DtnH;Ur&RNBbx81* zcWbBS6>eL3YmNQho0oWZo7WXt9a9o1lv;7wV$M;;t5Pyk3O4(^-Lm3Z;H52_uGwEW zsZ}uPQTLXX6(>)#dwe-^FKnaf)}S--Yc2mX{HSO9S^no}<)gsu3rZf#<+}MjTU=+Y zap}>qqjj?Xe!uljx^c_T;?Ku#_dV~%{8@0hv^daM^wB$JzFEn>y4Pp89lhau;7rl; z=#n#w%Pu;k-|wCEs&PzFLR8Hx`qTpPeT1FXW|Wjh^UJ|0}zyI6p}r ze!B9ro{!Yt6;F1r+?Bhz|7g~=voF8*{h3;REHE=TXQQ0r;Y*WM@34P#zq5ybs=7?p zRO@utxG3}F3EA4*?%BD9Kl?Q`Hk|l+^wjOCQp>J+vv^HrbJla*5S;m{@NVEqo$r%( z-d3M3eR$vQyGB9ldCLy&eaF9P@5XBB4_2E4YgKE-OYFAX?l*i{9AWc0={nP1iRC;U zu9ee1DXn;Vc$aw6kB8!3FijDncBv0 z1Rib=j7_h2Rj_8$+3k^0B3E_Sc1=o|Y^1W|=DM=WmaCnw27TZAw7={F6sXEDIoUkie1a*zAk(HG|210r|`qk z?RS>9=jxkZHJR})H?P%Z?&5XH&WATIxgitWEqCeAp-qP2sr?4WQ#D zX!VA~yIVzFiZ#|foINRe>FKQKD3L!&mDlE`W>iiWnH&{${an^lpL^k_!`|)t%=P@j zJ5`rIhLe8WTz55W)~$J8_IABsUm~wldVl)z>5cN0#ePa%rw>iJvNUhswYMt+t#^Db z4V=%D%BjwIv~trNHi?xR*f&m#o$%Vn;Bx=I-yNybBAw=xK931h+%_whW$BK`-GM?u zE@^4C974Glb8~*qd6>ud^w{D&&26_AdFJMB->9;EbJ={3dVaMXQ&)Ruzxm}B^~CP# zip;RHo=&mT*BNyBZM56X+P7KRO!xW9HnrGURrS{X+AQeSUZArp%cc%O#(BmK`#k zx+m*D!^xeoH{M$u=iO&}PkHmUXNAfQzrN;ApJq2g?-`x7wW0F~1pW*w&)y-dPUi`Q-aml>7bItxUh<}-7bH6peJN)VGzn`DXelUd$<`0%dmlU|;`C%f2kSGE4*c&%qQ zb82z3*R{7cp3Cm@*T&gYnruIL*6U2xw(|2ut4;MDvn`mq|0nM~$F?{zLe9eVZSmM5#$-Rp_(GF>x& zrO5pi)zVkx3b&q+wY;|T(vvBv%U@n!cYBqu*LUXLj=(oH&m!4xhjKPb^j%kdDx6U+eOzvK(*4s?!R#ld%1`*# zJ>zro(ii3HqCck|JjhjMS-EPSYxpZU?vm(fd7qz$mfVrqKkwG})2B`pt)I)SzjxB) z=(=iC>Dt-pd!^WuL|a20natA{cV)~izxsAZ?Bdt2OQn|2F5h=@%5H7vFZ`#asxFsA zTYOn^C+pAp%%Tr>&g)*U46~n`P${}hb!O7EiFzOROq%9+bn}X>O%JW5V$waPOf3tE zow{wy%H`2PvkSGHrcC~MaNSw^9 zu4X#EZqwVsNvbW!^DpOq^pf~?bDwgt#+{&PM=YjYn3lUXv&>R+$5Z>F{|rG}{=D|O zwr2O7CEHGE?y#2?O_}{cRrT>3o2J+N3;8WRty@@ZzI<}jyk^U5VtMDpKItwGKYY*k z*x3_DRdzhp4!$*=WyP~}rf!`_dM$JDN$k8 zZ@Z_M>sZUo$<^N~7tOcL%TqN|S?8O#=e3V>RMw;>*}nX6&Tdud;<#ODOQPln?22CJ zu61NH1`}@YM#d<0$gq}LgmvYg(u*9A(M>A)qRE%fg++d@mQ$3#o zzEld{bDFS4S7nJtPfy1Q$4Na4)j5|fn=4f@{YaOnk1My|P_rU+PUo-a)-NY(j`UW4+4P^`_I~5P z&$BR|pYkz{PvjT=QD+%YU(! zpR)DGk>1eh+v!UKRrHS-@x6g}D%KZD%e)hJE z_dgr!4sYClPbU80XWk{1sqTlRWAa%xJlk?tl$-Oo@rv&jMn74nCEYkD^sMTt?drhW zX=a&);k{4F(p{ch{-UWdWvcu8kX$KGx8Qv%vpsgFX-B-1vzi;U>(Qd8?l;xlUdHzA z*52?be!}NposBE*&-b0TQZ}jLow;fw_on^QHifGnZ4@uMQ}M&*$BwOf@>W~4a=vxm zy?Jc)n)>e9>!wGjn_jiCK+4(M$g*K zyW_Lowp*8P7u`B}pzO=*joT%om+n01dQ5A!OxUa7QfqUQ=ntEoF8W+jTzQ`DUG8nm zyvmB3M!U1#-Yg7zl6NZJruVqoogJ6g?A?MT{QF zvUBYC@Zp+Jb9ZxL#+Jik>W@k*QWd9Lem>f=&%;FQ+KMR8^b5P6Ejm|bIep6BSt*KY z^Fw3V0#AC{WgT4i?r$|u<6rfq7GIcW&Cx#kV_kN7<2lYhTGx-{i9R~8((}WkXYz}L z&R)&zY25lvWXb$RR$tVXmzu0pzwt$N=PkXxytW=EPOp?Z`B^rsb=vhWLf`lWt+XZl zSKI8Do_F=twAwn|`F!p1d$lvumhId!QBX-)#UFi81ndLLuJ-xjqEfL(7e&xi8(^sWumi1kkeD~1b=Ff%=Pi0;k^=}JL z^w$@%I$r7)Slt^Qc<=GINw3x_nO~e)81rWJ2DjK{aZa7S9z8F`UAH_6xpn%)A|t8V zsYTE1O4dc@8T(iLFq^-mmYbbB>S?q^)zlUDj!n^ctQvb;*uN@!lvMAwn{pw$>W)hgQjxQ9@tLBU1_l&>Yw?71MD^Xd$9D7Z z*T3*Qx8`b3$`xsUS?_+$b9ZdE?Z0!(SGH|R{MU@HpAEB~SE>BgE-JcuVd^bYzgp!h z79Wka?5sa|&RqG)tcR06syq~bI`OIa&#q4;g*&^a|NM3+*JR9>6hotw==LlXw&*p|BnBmNmtG!J6;cY5mS0iWtq3C zHg{3YnUm@_yrQyPYCCHS4qb9vDrht*@T=abM7t$fb-%7IPvhIL-8=iIb?U##*;i~` zzo-|EZK zJv&pypkH-hZ}pZ+8XZJ_G@zB!?e_J&7*62w=A-pwOb{2$upVumH)J# z)nuN$boFI<(5IN2@=-PVS4_8Gx;}CL@mh{+8*>i#2VP#Q>vZnzq$6wIUD|zTWxLHZz+qFj@KAd3Ay7Ty&pDoAy?e}KPak1*^HQu^;(!4B_6%&>j<<^II zd=h_h%5mbvC2faaTX37JGx@5mP1F2ok!Y#@>0;+&d!L`%YSu6MoI2yq)Sc;{o}Ni{ z_fxM{mpZffN!j|?^*-TxZ>E94HfTpto+us zO15kFm0jC%)*F|4q;7J$kQX}ZN@kGs+oZXkS8U&dR-n`bPoDkBHBkL&(W+07-o6XBOn2GVvY`8+ zN=Nr&-jckv+iz9-=5gKKal7xulD)}BpetQmo6eZur+lPA^*)%Vl*{KTUcR7p4A2Epz+w+V1I9%ymyhle0q)Pub&ns$l(TKkci= zy6@XR?38q8T$w-9xhp%WNINubvq;NY&oq-?ch1KwJ>IK+(>Yz=K2s&@o9$MWJ!>zX%*DTMx;8dyBsc}?)VL)(zPp$1z@w8WRmM=e^ zKY6QDb7{$^-svy5@&3^Z->&_t@9)Xju75gHLgH5~KC;JGPWo5g=dj6VWh+l_U2-<2 zZF*}m+nZ_6>#nT3vsd)X&bW6=AMDJ_xE_4m{Qb=5@(1oM{m*b%ho$_g|7pWtA$6KN z?={Tdti8YfN&OK+H>JaMeMjF3{AW;lr}%gEFZMUVoxQuBuD^KJ{=K$*ROin#uiM`) zpZ%ZVUT^q^-_PT&J^B|gXWH+ia{rs+%2Mag#!ok@n`=KiedoOX{|uXY#XsliU(=mp zaNKjw&FXuP{;m3%{OjK3`#+`~6V~^z>!1Hl=eIe(as8S4Pw|?yA6NW2CYiVQvuTQT z?fgR-#k1ZoUN3&{*pgC{uST1%&b3*hH#c~%wb{qK=#r^=)dmLk4!-u{!RM|Q7Aqf3 zymNTf-H$t6|9mk1$dOm-YtFvy?=3-VxwY>s-cE`-V_bHrZSVBh$t#z6Oxm|~bBdeKHeD#y)UP>M^UVM8qV0!Ii*B=%xLT97{`#`@i(4$y zm13ixtTy`NIk9L~?~GNe#g|;_3tIkX?n^uAtKYWAq=(---IVy_@Q=#XQ>#u&FAfV{ z`1bFLm#VW1l#H*Xow*O$|It3PeaWuc)p1?3^UoeRIaQHo z$-JX^o^JR2-WxhSyZ5>+DnwJuY0A{4os{AXBx z=xp|v-Ov7<^Lrt;efo00vd^EBv$=%27k8c7EnU9tedzJ`NwaRv_6@GOEq&B^nq)su zMevsLu$`@ZaYu6h0B5XX$+{jq624nc-LN`&~Zf*Hh#sAMmTQ@wC;HrLtC!=bm0=t!fd~ zvi$DyoA*o>JlS^CTy?AR`q_HLN$=jwOFMaQHow7*pGR-5e0hCl?vvlQoilG5sV$RI zjj;Q8cwOSpK54@UwZ{hDCfi!nPnfO_dM2}G^{1VdUQf?Ynw51?7RZGep zFO4$oYyB4ZUZpt?LywY#=%3qJJ zeDyi@cl#j~jv{qEH!Y)0PyXm`uGeQh<#fEp_{aH>4*vt0%gnAUQ$CS-Y|fW#+qawF zDxL6(^$J_2c6`S4S@C|Sdo8A_oKhi>u$5!o5w$nJ7-!r*;afr$F=gjZDpYJzU@rbW@ zSQMXm#A~;PyPLw3&kv8fyQr%E6iE}9x39G1Ra#nF68H0+mc?SO<=;1b%c$D^Vs4t) zFa1S3wA33@f34&4zn;7CaQ)NDqxsvM-D@XrEKGZP{}PY#Z z+Zj^6FTelhY=3^nf9;c**56P5XE?Fky>_S7=Qrm2U1ncdfAQ&;uU~h!^;8Me-OQ`Y z*!d$)aHVX`&#A=^Bd_nitmO-Nz41ogjybb_cHSJv={bWyy-mq$rWY))*LC8NS8V- zaq|YRn7jP0Ka*5m*_@Z_PPr9$%kveDO^0a#!O}=@V>%>moss1>q+3H?d-@-kQzg@Zc%UU@t z+3eS1yHf_!#VxmNRjT2tj^w!%J1JnwwjDcJ)fb;j%usS(3I`OJ-?=IzrvIk(DQoE>k|c6aOcLN8w1l}DEa zJUZjK)W7B7_ut$`M-6m>gx+kbxvsqQXyL1GC(~9=k7Vu1T6N>!K|Y<%o0BHErA&U| zetr3b^5o>Zr%!J0PArVyX)3Xvd-9?mIa^=PIye9HEWLPxtjl3v_Q+&Ue|P#x?ycrI z_pWVs)9czDcX(6M&RM#Ss}qjT*O_sww2V=7=H|0ow)tF4w0I?`6uD`#io)#mYfQg+ zoakQexZ~SG9-D~U>(9N7_S&bhs+!}^l`P4~nB(j3pAB4>SC{hqaMaK5?<|A%?kiat zyk?hOsJ`9av)@FX^>)tuS3E6k(o9p$v@3IFeJVZu_S==ED{F6W_SrRa=6rw4UhnJc ze?Je}S^0j-oV{iDkJ`J)s$2@|JSkC?5}SKtbKa#o(JaBHy7ipVi91v8u}^=r^PI8v zg(4ecmBptGf9{Pk^#4|p=3%LAn!Kb%Hnv#}xcNhLz8@5lwP37cE`E!!Ck*|%SoOe#lvbbSkF){mG z?(LpC$)Zt(Dff4~th$@M_rfJ->)Vq0%G>jvJgG~Wx_v_Kn{WRaw7;0{m_FG*(k{E* zyZ>0ltn9t5eL?0@%v>gudS{$8epTz^U~_S0%$2Ru)1SXxZmzaR^v+&a*H?1;zQ|Af zXsJEf&}2`_?ln0Tkv%K@%tfQ8YusMEv+z`I-kP~)0h8wFJrP-__o?1LZ;P$st~vhN zUFYJC6xUp}X5E*2OXi&7hC{L;+ilO?^eAnS&MTR6+gs(^q{&)wQCH`6u6_I}wDyj= z=Z}Z_X4Td;*KWm$bbpWDRhhpkR`;5vcALJny69=iee&j7r^5^Hi>z2Q_lBGB)bMjp zP3}sYdwz7ey5T45!DN9o#1HAgU6n{-g(q~$0Eh_jXRhh%DQjRImP3>*Y;=>cgKf&RVEfe z-_C{i9g4Npw0kUjYjXaUO>Id_(=OdDx%XB&yD)ZY@Po_KcRF33EAl4E^>WdBnc(HC zErZK${qR0+s`+*DCG&tb<2f5kJ}HbZx5?=-8x9uVxCJ zI6Y;_?acSpW#8t#_#`$l+vDkLv$alUk2RJp7Fo1dWTnKC6_dO=RVH~SX*1>-2Yo5L zUVN|capkMKH~S{t*>qj9*Ux1|hsnfSp(Zbn?OzhO(nDoywWFGk>HJJJkD6bS@>62A z)OtOexGLhx+Ujg`k(Cdlm+YS^c70XCU#}-~^cH^-TYXaUOPHyQ<*lI7C3?TJ&CTjv zs|wXv+mTGz;NBu>DjBQdjzZR+_ih2KQa2+rx55C7VFhBO7T{gGKv&jvg??v)}^`KKW~0t>^*VP zrd8Y4_s#lPlXhP0a`k_PGwdtGzQ*cLt@QWXeZ0Ny>RGSnJd;~4dmYocDy!=9X~p*- z@77(L;?4{eaMIZfRjqKZXJND5{o$r=uVwuzSO&3}B)K6)huZcP* z>l?X-Ei2BtWcu!3d24y)?DrP-W>-Y&r)qh{bGKHt#lKKLQTO?2WwD+MXl~Q%ZLD2$ z;;E}keP5q{zhw1ZPm`5fAEh`yUy{JUz#y$weP{h${weiq&Zhro=u2Ic^pgK}&yq`~#Mb&P z(c2sGJ6!x^`OR$!@=ss?tdITluv)Th$082fO#S0x-=w8xPnyM_<}+*7t2fdGujfpA z7Ttbv*@~=Br&gMutobund9P3Vh3C%7@2=GH^qCp76lQqgL?F>`HgOhH}Q#J5zOjccfVX5GfDK{m5vfWO9taC^DMt|w; zpx)X`S^F0Lyz^AHsAm1ys(eG&un5U(`WLTfZ7nh`EV=DA*>85)+}yL*qAt$9V!rj$ zD*cR9@5Oso&bu^YZfIek>aE{aH`nNN=6E-oS=}mRf9iE9r7m&#&1vb$dQWGsJoY5* zvVT?PTlbgZ)3(aQMo<6n)kgBzJJ~rK+K=)0m9wM>M3POlHFe!50_ z*0jBy?YZGyf22RU>38K{>B{N0x_`D}&g4sWiO+0KuF$p$bJ2afT=e&avcd=3*5_9} z-SOtuV3IyId;Xj|Cx15XwDmL{!7wI$9CE-_FodWyF1e1xT4UdZC9G-*XtRx zEDDsAp6ihtw{pTpliNS_AM1Ml+EViMF8}%G3NJ1{<14>Z5>fwauG10uF1;re_dg{p z`Rly)HFwI5W2-JKdXaWe|HIU$iYsPbdUR^`$N7u>g15YRA@Q;Lj`r)R9!rcA7#JA% z4(Ic~{n6+3k=-NLQrN4^RiD4TH|?E#rhfI#!@d#60)D?;b2e9Y?%bqh+uYKxcKWX9 zIdS6Htxwxl|9I|j?0nw#)Lgf^cXBy<%U132m7jZW-zV;u%fq*x3*OGhYj-VY-DBO? z_jbzLANTzBA$m^k=^qy+-MA=Jqi%6(pRMgVZ*Qp|0rICRCvV;OCi9xmEs=L?59mJI z*`)h8cgCCJ{@J=40*zWG=L-JhKYh{cU&zI2)9VBGT?*PeQ>(h;cYM2MjxoOn-_kqJ z&+1P$SrN0d=-ABDW)@d=8?C<>zwFxm*-57!<*l2iyQ{Hy{=}|l`c~%mzIL0MNx0bb zo~>OKllg3k)33V^mhGvQ+t~2hCF5ODy=&FW6t99we`mK$o#m+{_Iz#p$7w5z3QD&g zJuF`++xeeCJ7;h3<^8UFbLyKa`MKrQ?(7z+Nasj3DR#a2&G{@JU%(lhM)T!_M5xD8k&C^xo7N3qSJT+@e$d5f zuHOAE8!U9j^zzk%+fFRs@wa&rmwu9ukG4$5^4(trw``f6w(gKn($SL@*{)ABs-K_9 z-n-N4&7w!s{xhfut*vR<{L=hP@vHK9qiG%YTvRp`YMQ{Gg87X_^bV#V%l+QUU12k_=nz3=x z6tlL+n_3h(oPs7@f0uH8Ufp8r%ik`|o_ss1(CYG)Xz@S)>d8UWn&8pM- zniB7}POLh;cBe^n@s?ie!0x`(6@GiS+Aq4^HhE5bdQIJ}ov~(n ze%*VzO(<{T`ZsyMchzLQkGc5QjG z$EnFX+pDrTZTIND>F#d~>s)(f-Kn=G{+D)NoIkmg&-JQR=YpHt+t18OGx@Y!Lshiy z?yhrR@->z$H<#0NdmQ^*$n)f}jb{2E=U@MF^L%^SkBytM?6*Z-Gc(@2aprlW_Brde z9V@C@wtSW0O0{Kkw;X+wlYQ3H$jIgJ>nE?=wLb5gpB?yg&FpfaM=c?b{s?uiJFm5- zrswnTi|MmJ|1@7GrIseMWX;pKX}T|#U7EZ4L*bG=TH>O+SK9Y&7j{`T$9t{WtGdu5 zs;RFvI{tZFEt?bVUuFAeyJ^{-Joj5C)nCsRs!**cpLgcu>z$1|GzFgRK9qY$*SbIQ zxT)#PbW{FsGG|rXYxf<#@lM!QR&m9yDc#e0eob_WlAX3I_f*`UJv*BN`hP9mS@TT) z@w0vQcMkrJ=eho|?C0H=yI%_J%=X!N@t)tl>juwm#`okqzCC_y$EuB!{Z_s6iOpL( zN8*!r_P#kr-jBT>rQGM-^15ny&|TwyTXrv6=i_5($Sb7!+Q!Uj{`##qFGrc|xGfg@ zNnEIPd&i_NpNibwrCUB{Pp;fNBYDxo-C-%yYLEP=@07c_KgmvfdzR0|`R?<2@4w17 zI&L4lRkLWC=!x&UFP+%E@vxMBa^`xg&;47sdt5Wx$(AYF8DJeQ`|_KXUb-9oNsi z*v^tXS!2~jl|_?oicEVl?Z~u48^k^vGVtX~o_JUAXR`e1!kg>Do=9wc>gVp7bW!DQ zW3juP?cA7aoi3T?O6R)Tg|GDNT2gjnZ?V)LBb|NH*PbmEe^6NJeMKdacA0QMW-!?%eSqa96lpZF~_6l z@b}QkhPBOIds24#O1;VpwA*_qaK%ZV`5C*uDsH!m%QM@*IO>e2wWL_h)4ieVB#*wo z^IH6gz2%?pr+!`c_%(C>>EbDGU+pqm`g!$~!YPY6wRc>9yh(4m?(XtMFIFv?WxDo* z-=n+pR3CBfdz5Avs#PxVF#U&?+WB=co_=XzGi~-T&H84N?tA4}Z)NMjowd^i9-Ta< zvfy^m?f4v*IqeI)h39Pg{WP>ya>=d5kLK)9R(;wuf5o>PTj4KTUwvJd_$BM<{iBy3 z?mY8-?si``ui{;sa^sF_>0N!i^49#)ylX6RFLr`1 z%lWWlZ;88IQ%%?B;>|m^{gKs+?bF@9X^z;B{P>&u`utA$mnN!iT|4XdQk5x7H9rY? zOmclCDDL#Hp;7L7@O6o$5-Zl8lwVL(VR1We!G%{sk3?d5wLLY};(7X4@&y-`MoWGy zx}$fz=}}Pkn`;04th-NdHA{}&Sjv_;HEYker5Ukxq0O=97HoDBRI*v>JZXo%N$1z1 z`Ca#yxYW$;R@Hp7>(=F~O0OH&WkuyatrvT9dn+KhmZAOxHwJ`+l?0v5cKp%-t#R?$j-xaJl2hi>m(? z&x{FiPTRI!bIY;ESN-~@7=}u6bcQP3TlCIo>4rmrlEOhBH?_?)G3lND)bxzqUZ0+Gg0I}>=5x=~i`z7*^{M2ReV*&BuVoeR<2| ztGAU`mMSWL)hgP)@b(wORUFfqX9y6>q*dTlLz9 zdviZedGll~fAFO5dvDgf@=xcvf6V^T*>@kj?^@*?UzWev&u;mm)2rrmC&l*_Cawh3eR*Y`fGp@t1k&`G6PYw$4Gj-o-xUdCncx+VS?$w%HH%b6o5DW<6yo@120D zr$hTIk8CkJQTY3e->=tVCs(CkcvA6nZL8>4)g|Rhks&dmUS+&fbAGIw6w|LKa!l-Y zhtaF4Dkrk;{ubZ3-dpZZjJ}hfk$#c6@Vka&T;HbDnJRq&uezewke5jf)Ib`>8*F@{;nE z6Mo6ONU6Qmzi{GD*Shv;8%*c;+_aO3I?jSNo>!dj^Rszc&h7G| z&0BI#n?2gTd-F!qwE4%vD`aXiWt;X)zj8MJoYi|-y}YGMb8~&?J=n1HYsx9vKL4J` z&?`5#E${RZdJu6k^NXfSrmC4+o#VEfKTeyoeN(<$c4cvV$*lD&>dGzMW9Dmrj+=cZ zFmxJQY4?}XTWe!_?!=m1TylBY-R056rPH>gedT>yv#R@+WYCNETIpMssfWgdc$V!u zw=J7nDO%{k*}iRa9DfFFnJGUb^IPi78>e@sy(-)(x#jY`S=E8RT%WFW7Y}>-$JU>% zaCdvX+?|IOyN(4+30bW2JJ{FsQTm2p@k0S07Khx2h<$ueIFbJhtB2{3-IH>nB$4^=DuCTB6*jNU!-&#N`8R&Sr3XMJAY>CN@qlIDDo znr<#r*6*xLf0mOfF>9Y%sM3Ab`^-Q3i+}hZaj-dhxi0ZS%=)PIeILaCEM0Qj`^UF$ zr|isjEZUtC|K5Jeo+Wk3;z9QI4#m9y&Pn%p><$5}*M<{gFhNs+D-+EbGU+=l^oa}C`vsbo% zyL$W!-;_Jjm;UZ}*|^4ZUFfXyK{MalCR|hXPS1}Ih`lwl%IVoT+Rt-R9uCtv9lpQ6(m=RRrA*{ra+etCUjVQr$)rE}SPZRS4NdFf7JJg;+5>6(>K z)4iVDO}m}@5^E-d;qsiUXXsc5@t5%0>Q%VxdF zF19K%e3Sb4&XH3sdC9RY3oTBD8TCvK>W*Ex>~idiH}#LpZmfAC)_hrdk+IL3&wGSr zPt9?{`mB`c-l zioVJQ{@n4(UNhwryWMM$98!1`S;KiF%|GSk#EDjS zL**R)EY5ZFEV!vMb9&s;2}SFc@+!|LEPl5uvh|kdyCrFJRQ|fE{dJOm{ayJ$=?yyLvQsckj3IcT*4iv|n5%Hszeh@V!QJEp^Yma+&Q#= z+tC{)FCX8z-fLz0*y98Hl%LbXpX6m7dRD7`=IWL=GwyUv4a(D-yxnBQk90Zhabkw0v`>z(TE%ip*ur*F3IShU_h zPdVdhJeyu;dwLYg-tNo3w(zoE`mqxQhqv?07A={Q&XM!xQSV9D<&XS41K-}czbR(! z%ZnG*=DTm7cxGp1l-ioL$MK27+o57E2UW4xkxZ_)|0DGuG+@xRH}JQI-2}>&a5eG zIVT+AnG@%Fx>ol@q~PginvM~@^2Opup8OG>bYps}>#2Pr+YjB%`SR_O-iz-)>ld4C zN?jEf)O;oXcG$u+-7U|1(?WMzPl`^ozPM%WRr9=}n=;=Y?b&|ey3Fzu7M^Xf*H7Q7 zdK_f9@sm}Q%Q06qb)Tb0Sy^mc(GKHsZ$ zI_}5z?9QdTv{tG>+8=TFi7Djc}I_@h}`@kZ{=x#!*&84`*B8^h!om{(ZS?W}^rSe~%`CVPH>sO2G z2NuPv5tk#HKiur!too5Zxc>CNS357)%+URCW&QduI;Z~`b{rIyYm{xjCqI)#W_I%) zpN;D#-C6N*$A5;^8^6nD`mWTR+xaMT#@Z#xX4AGzp77@7ywsHRTdE6Ax_Buo-Hr>{ zEq!NmqlMMd&Mdn%35))w&9b+yn_waS<=&3Zd3G)_dfqdN*DZPdHvfs=AGupOQ}o=t zH!fQly5*Ysv^XxAv&j>GuhzQwLp1E#wQB9uXV&VHTU+O6zll3H%`CT6U1ehL{m^tt zJ#9tfmFhS5if&JuvP{K2m2cLCtt*$f)LcJdb9nNa*i)BvwSR0+dopY8j;%*tT@Id7 zZgy#Da2)%FFX0z|U;4awmb`C)iConcz09hAyxnE$l1WeRw;s*n65N?raV>w9mtps$ z`7DdoE(LW3Ror;mFIaY+r}$5KR9^5gZjF-#6_#JjK1NrX7ep-Eu>YjtHD#|ke{-|l z(>iAyey7V*es)|5bXWvm4 z2?>S6776hxpq%59D*Aq^ed#=*x~<#y|5IPhS|3)|*!^VZn*&GePhbADZRfj>cIg_| z=gqr!TW-gS6B#>e3e`O^LcUtJJqr^3}YR%M!Aqa{nraY~}uy7drFS?!2sxb9YZH zoTD=D*ix~}u`6fa`H@ikZ29b4o?D+x($3y~ZhM-j-u2^2E4K-!WqI9Pej{W;$E76~ zpZ2<&+T04h*__n=_-C{?pY@iQxo5uZobD5@?z-aElDwl8)oRmEoUZS@TBi0#^vjeN zVf8MS&nhBbJ-z39u4s32o!7q9a5gO9v->Ti;{@u9xkMMU--4|b!zjVZ$UwZq+e+Kh=`7=JOwLkhjU3aI% zw#VUpw{N{qb~WeyQ}Mc}ELHRI;luj1L50U>{R&P>E#nD&I7v^ZV9l{p;VwtkE;_gJ zd9r%G;k&jKA2QbJDErkO`m}V$sm~K<%E_LLTDn(xX5pswTV=}CIk)fBTrRbr$#>Zw zkDL0Q|D4ah(swD{%j~sXd)L|B2B(g<)NXl~bf@<@$NIjhrwe!H3BH|jJ(u@EW$>{# z?9+cS+h3?=y{Yp!@@tAwe|hMgoym@s!HImoB~GsTeQ%Au(!6*3Z2I21`JT+wN=?5} z=$vlqYjtex@;jG5Y2S_Q)!M#%uj!MLI@{x|G41D~*85xT=brtgReXMPb$3(g!<}Mt z@}lONEw!+8-MYc--`}^|G4F{k4b1?(XR}Et!(%WNodr&WqRl z)^s7i-EqhM_B{3X%WKNE3fjZMD@WEpw+O-{=!$d?CE#^&GZ`XWETU`pULH4ZM>w@$s?aAHAMR zO}f1)!DGv$m#SAxUp&k862Hs6G;;HP>04h~*Oy}SLWWku3cSBZ7U9zD5tD>$WN z_Nx2Om`_{mioI*Tq@vQY>h4jCHD`8bU9+8c;_mj;Q{nmNYZh~F+{m$y-R|?bEjc@v z6`z$a7uUKv%k8to^!Ve^v(B1NPTH|b#(FQ~VDd1uk{{|u%Jc4e9FJrVQj$!^yR zzt?mXrtnOk(0yaFS*Fqt=cvkWd@`pe%>UB+>vx^zJM~3&K1I8f7stfTD0}$T>foP~ zM+@%%j4OAqHXJCrv{?8zC#AV{8^OwrMZ#|#JHo2r>RR2ly;{80oJ->P1UsU_5{iU+jwl?qKV!0OiZPD{*>D7t1WEq&Q_|9-g_`v>I zSEAK@jgQJ)dS?e{5L z$}eRmye#HuzO~OE?};j#3s)bQeOtS9)$%^x4_iOk*cl&}_nX)A?9+DJeJe7>QuQLQ z-KqWXSp0CZnPK2`>DwMpj%mF~d3m&aU$o7wd;5jnbKc3HwOQhJ)TLjGV~dzJEnL+( zdF^BSclN2F2OP}<*$u0=8D@B0^7T%!Z-xu(Cp~?8{X}8gS;MI2m`8V_ zwWmBUR9&e*xvFZ5O~>#^_#8&ARkxu34U!b>@+ms*C4MmYZ?;)w+r0 z6LvDoD|1iIJ7@P}e|4LsWEQVybl5}9)fRs;J%4`MS*fvdb>QQzTG_|1&#QRX^|So($cvipql5zdS?P`_#yKajh*v$<{iA+CLKmuJYTUbr*;fPS6akw?b@m%d&8%k^8^nR{s)+vB497c<`Anmys7 z{F7zbChjL6-`(Ug*~H|Ol&7G$lQjROv*Gt=iF|q8x4kegu)6bF;C}}1-ik@?J^$v% zH0{h^_2bUd&ir=&>mQ~pyX0B@TD-5^du3I!Ln<%Hu31@s<4d&Jy03xS=PRGj^|C7K z`uO0|&e{+E85DD;uivD9S8)5H`6~CG>n(SWJlV4@DYGyy)ZH1I*Fg(lOJ!{ z7I1mjJSJ^z#X~tAooxb>Uw#O^(ChwsW^c;=!n?b*`?t<}nOPBV`OEK!2_XSZacj-y z%y^}J_KA(GCA0XX7`fvGe^wsTGm5o*Srx9|YU(wqtYVJ_?|Sa3nw?yw*{#N0bIulB zS*kIwZlB{smAy@6*~@qBy81Kg?&|a1%F@q`J}%jF{MEswk2B|qRV9CU6>IsrEUj*? zc)rKANmp`AH@ZDNX*yZtlBdO!W935LXIoEa=Y8)#Uo&@~^zkkGS{}98D(d*%j$HNm z%rcQA|0fb^n^RXzxuCNB`s%>7n@%r_PdTw?)$+*FPm;3gfu*XQOAmADP1^Be{?4|i zuTHLyII)+zQmf|R`I#|i>-54lyghbC{?9w-nxZ&4>3~_&F9)tpDGQWsi*OWFoOa=s z$DX?alk_K;9t$d(Jo(y{mQ%|9u2$+lO4p$TWFCPnMritOex-~275B`4jdbpA0Z;mhxK zJxTIzue+WXT2QxquDnp!hj}``%D!CxBY(Bya~qFENnO&$`&Mof`(j(AD-G{mF7`Y7 zUAHfIukE~i;S*xJu1T$L+SNU=a&_L(6^BxDWM7`jdE}itC2jSLy~`yxNpGjT^YY(%%6IRY*IPH7`=on(!?(VVXF{@1ZToiM)aToM z>n#;|yidM+c4sf2@p?i13k%kz7agin)kxj4RK-(C>FT@WbMwj;T3@%_v*gL2O{L3M z?v(rVC4S1sO8?0lOYXS-Tc31vw?T>5#;vS;YSWKzC!!(w|Gs;=Svh>$`NP|3gWe5bu`zA>X^Ft+4dE?-i61d!^jx#4mBr zaITRdHux9=gWU~W>`kfwYl)K?bJFuzO~lj=GU~<4i`>- z^YGE}z}!D2kEVUrx6{AFc-HdtO4BWC{r+m^?$w?i9iKe4lI6+ZSCnssvG+zFXI>z#tNgKJ+}-I;ps&+dt5tb#VT9)5qi=uy|?MU%4nOyA@u z`@Xs9?sYYjebMpAEp@%6OjXm3-9A5EbDQhx%cy>twSGpAwwE{8JeE3V)Kl}yze~%{ z#_0O@s0zu??@jk+?KV2}VXeL2^hZV^p(fV7i>!C-?^6b~8bGH>IYlWn^t1MkU zQIyZ>cj`S=+1I_!H)4&Rt(mwmp1Pb90wQeVIOUuay<+ zq6C{>adY#?vxU=n*VOoQ`*#Mn`c-cGm?&{_*G~66g*_=hVs8wtf)v$GXly|?tFh)ypl-Tt*)Pgea9=-ojUUN&fJ~R zbFL(Y&C!0V9CCT7RDR)kPQL8GqSd`S?kQK6OMK6pzGU^`&9^d*W_`Q$YSPNK*M+r> zvdwCDr?Y+A*>-EjtDuyti~Ut&r|dYnK5?h;jJ2FC~>^e4Grl)GzpRJ5|a%CzsF zLgSc^?f16pdG=-b%A1ip%2IZkN9?tk$5+{{IxdUmfCnrWV`4}K10pGmz#d)EZ(s(TGLr2blIzrSJqcdCPht@c3$@9 zuJztK8y`x9{yg_~o$TD*Do?j>KD+Mj%G_n|o>t}lvV8pV-`}lkp2VMg*!_Kvf$`K^ zCzm{qo~CP z>GgZ9$L7U{uBV-ewl-gz;+}tLZOXBZ-)pu^i|UtU`_Hh%d(Z5XVwI`h`f7!HSDmxb z^R$!DyK!hs-nyxiJ-4iw6uN%Sy~%P@e$9_ro;|(9rg~So!lQfdle3;IdjB-W^O$&1 zh0H=$j-3S&E$`fnCeK+HGpW72`#(dC@3}QqhvgT2_kCSMPv&TQeLARfIiHy7e3L^m#!gbX_cQK#=YDy$TrP6@lrz42Rjy{s z%$0h4*Ly>py2ts4AM8UnpRWHb^hA{Vmwa%++c&f3&7G_=)nf6{deLp$wmp77W7}^< zj_XB6v5!8V^r&%s;P>L>$K{KI)2khgOwIOv3EeD~d~@5SyhFA}-vk?(?#*`YHqXpy zEeorCqayglf69`&v@6O>Gp?_9{BU>HgZ^Dp%YFu}+3W22OY-FP%HJzzmCc?gUhaQv z(uw^cx8CJg>^OM2Brm4yZCQ0a=R!UCppY*Hi_5C@)^4hNA9AN)*Zla1w$*9(-hWcn zJ9Sa+Kf|Yev$pJ&I@=ZWxL;(?nw3WtC(N$>!BFHFd9USM#midjy?ZZ}KdtZF_W7Lj z`s_2Ge0HvLN!%XxaIJ~tWaBBXCS^KFZ|-Yylq{RN!B*>04lHI{u z%FF$hq)sYcR#Nhpt!&|@@TjIw`O{ar`iHdCN>m2@XRs{ImbO;4o>ubXv|8@=ZO=}Z zmHnBt)3i|UYVNGKpo6ZtF&Y_qEoaIut)FEwamC-dWu|q1kC%N}_B-;u+|+g%UaNCD zq9O{@S350TzH-z4ZL1e8Yx~-|_iB&UtQCSMdVgLE3yNxIlV(}$<@?fTo|o{I6T7_> zKj+sxXt;3ns@NMPB~w1wL<~7 zVy8UM^)oj}|1w!Iy64ND6{p^FJ?g!3eXj2we@RuQXKwGf z^L=6b*4F#yckOSsxKn$1^1G{Nsz0^Qm-z5~a+`C|nmM`dt!@EA-wy^K$y;26)I_^RLgWTW5ny3YB|e!M;7OL}au_WOsMuO7RS@pfBA)W+z&7SDc{ z%P96r9<3?KyS4b{&7&5l)84Mky}LPcgM{2{xjxGo6HcDEuAdY)H9PAaTlG#U&!tD+ zB*$%iuCioDp7+`8u1Racf@>^n#Ld=CzdIvdVW)b1=e6DImaY0Ncx}d-$0wJcIk}hd zns&~n?jy&NX6@6n)e2v->5RCV?#~>Bv)67rPTqN3{mXM}-z#fh-8%AidFn@}JMqFr z%Z_UXb+>KxGF|PR75z2*lKW-98_vGh4 zxhGm*v3;v;I`wSGu7^V1mvnFXJ1Yk+H-290c2BO;rd(zH{m(&fANpF_tD04&^rn7^ zcyi`m+N)`L>!pLHO5E6JD>i`X_(vk9&;M{v#j7Hogt+cow$3 zV%nC~W&5NHG#D$KTIAQcWqN1$O&i8(D|R-|R9*9VURdh=+9$hrmOqrZdvf-iIqST%o}Mn4deZFW zQB%>Vi%SoE^|<+u#ZKi< zwuZaA$DA%)cr0hPuF=g>Q;jQ2U4A`RE)LsV7yd3K@%!ZJ=($HXZ&X=prLs+>O7zlw zuk-HzX1y#knWL*b<8ga0S9DL|hb4FB-He&DbcO4wCr=~2LhgMFRGBXSpW(unV)yUo z9{p#yy6oCNRX6Kj%bwZ4{ID{~1=~yYAohbMdcj?Eh2>EW;bZ4LkckiH!)RrNAHQt6KBLM-6-jDQ}@_~Y$N3$@0HU--%b6@ z+ikqx`i7rD_v9t-u62f|pDUkwD}DKQx396@^4`ZUSI#p3lz)4xxK53zsP?1)Ki@3f z70WK?s$IG>?=kzzpb(cOGd23pWEvUSN1Q&9WO8@UJyliJh%Fa?v8!F)_kI1OdGj5& zSuC$N^_uGN^X=WT=}(r{uF6(V2zXxdF)AuxndI8-Rxi&k~%bEe##(tE78{KV}y%!;Re>weehSzFuUel+>p z+^FB2E-U4xnw~qUYWruo*t%6wJzg_zmD%a#`PFWj`er)2{uB4sYTcjFCc-h#W8-I7 z#=AbXIe99otdnQPjriKQ@U)i?KPO7PtugGnGx7TU)4A`~g?;v)6w{g)dG7e8n19E* z8EWQ7e>@-e@a*P}M142a5@*i&Jr|Enc_ppnzQm(2E;w+-*Zp(1`X95}dV6E(cFkSY zGk$LTdTIUCc{i%`io}2DFTWvSzVPt$Y$?5`q4M=r+w;{Q7RJ_>58&nrq-J3cG-$(_ArWx2Nk115QSo(ruGUK$c~YjXFkZL_AnG=FJW zy&!UyoZRf@{*|e(g!UFIojpBytH$G~2R*U7CkBN*FMD#UXKCJ>pzT(Nvr^C9s$-gS z>(Jae-x$7axU==lWbXCZGN*4H*(ABL%vtbGo@?HLWa-7tYDO{9uRNpn+}v>UTTJk= zX@1+Sm+m>)bNfh{k;3+Z^G*I&=JSM_|1RH{zwWTa(|G^B&g{bN`7@GNW$ZLsFTK0( zlg^>eEmuCqzTOZEw}%VeeXA6# zd&ckQ?|xR9^U2GqzO>bo-Mu4zM4CMfS+!c{bj7wylW&Gvn(kfCck_?dqjTnm=2<@a z6}Mker}dV5UQx_(qv?XP($*VAFF%)icKM?6_e%RViKNZymesR*bdgWH_R*Dnm-;H6 z?&UkLmnrt;cJbrFyr8+7M>Vas`yJV)y7i6Mnoq%NYeTM7UA_{tul)4!h->e)%hn{X zw4QslJMiIlJ}o`XlOJPd2X>XK>q#ti$=Lsh{lqJ~h~noHXE$?i^4+&P>(Ok61GL2^BBRS3EB8Hq19STJMrk$LTFcuFRa|bEbv+{I98dy(8w{*4sAuVfTj> zt3oSguZ|18F7G1eb@s`+*^ge{>fN3lICXo~m#D?z`sczeRA%{l-I38fS?0wn>ffGL z9=>+x;bStJUOws#Id=1Srn+~~Z0CR*r>}jJTe(T>QAmqROZk!4r#>%TqH*HHFNsZ` z+CmI|&gS}icEbC|Vt-`nQfs=q^E)Q*RW6ZPm+~UrBs0RhTl1xP-ZATJ^O3&OlGb?T~ z+j8v{t%}bsWJdRzEm?BX%`Mt>)dTn7%lQeOm-lYFRL8zg|4OFu!MypOQ}4Mxk2Rm2 z>v-pTUyPm?=f!jPx9(>7w&ZzUP*<+W?A1P(ADK6)cPvctJT80X#^%zVT~Ff6*|%=J zv@twT&ghQIwMC1S!m?j%x|1_2XS!c|XSQ(8a>w!Qi8D%+FF8-E z`aE&<%?ls(ALyFBUCQw!X50JwTPyvh$F8mk+rad>dkYy?9Y&T+11KL7p5-th!mQ%$YiDZQ)OknHn+Hi6Rxw|Dvds$qbhMu&nUDz z+V}h)=^B@*C)bvKZMwa^clW-pq1K$Mwr-eyYyQ^KDjEnSCZw;`PD+FZ2XoJwrsVv^okvCgP#``_w2KXljGXon0_|a+N){%;`NT7 zm}S=W)IQxRTdi}vb;}~7Lp7TVU-(QGuXBE{ec{UHH&r@QcPGCJ*Q|ba`VV!^1 zv3Jtv!e0D-@gZx6!s2VfRsLiTaHQiK;5Z~dI>0eHz(L;u4t+89z1z(8t0jlUtU5J! z-YKuX57U$5+D@Oj6RLT{cfF%{Txa67i&uWl@`*7lwb>KpCbV|z$4$5P`dxix%y)e! z|FoaYUGIG7y*B*MaO&0)^?06b7UxfEY=4k3)52fZAo@|7UbtACV#2ypA-_(Rm2^jb z@svt)%UF8s?dsD%wOMX)pL_W8m~-#CM|VHxFVCyn_4V5Q*s3MQ>q;ia9w^$qIfY{iai0>+P%cirW)?Q<-itAHBMA>ZKdACF`8*I_s?a%+g|OmR?`U zeMZmeNyXc(8b5+pDf6tc*&}^k-t6jW^J_oO=^4c>_MFr4TN82S6_0SoiF=@d9>_{a zILTKpd~)rEJ~;8hNzYp00KW*y8P{`Sy_3t{9SVCk`L^3*we6BS zKe~L}e)-RqM{TahWDVDs*Qw6V$!TUzx7c=8^TVz?fjwS*_w+)GW4#M|pYN2t+tYdd z%ZB7hagDs&AFO;_==u82y29(0@BOctSl&3)`XXko#f~3Ao>p7tq^rHU6g@SxrhRIy z&KK|Jdb7U<%ZEOz={48p7T)u2YI|MLly|PTTb4@i__kP7!Y?@2&(ApU;reY6((Af9 z0+Nn6mU#1+-4s9U<-Puki)2~e)04j6HecN%Q}HPA_ObS@EkZvamw=zY-rnEKH}#>u zL~>^C>!!RtyZo$Yr+oe}bJw=yp(6`6rX2MvR3`0UyZ)k)7_H_3Ins7;!5R39=$&D~Wsn+}X-Ip0xH%Wneb`qxW&&T^rxS znWk=6uAh3sw&d93qic?;Iqmne+-%^badt=B%SGp=Go4#)ys+U`bRJJ+DwmJjiQAf8 zi8`CEtUoo^ZU3^z&c0V3-%mcM${9APC+z9=lu0+HW?Xuk)mi!ZCHsr$rN#TUPdvA$ zv-)G%=Bbu3CpJAi-e2}+ZtRiC!J94jX~q-=_blDMyJyZ)o$1HFpSh#v;xX?|CSR<> zmY9gw8`}9fu9OKZ_6_#1EPQywes{L~T+!)gJ#(KP>)RgSvN&_I-9Zn7S8DAR%MVVB z7ux>KCG(p5!v2sqaZ|TXUHoC$%cI4*W~(=D+3;lYho$Q7o7Pt{REEFr-_CdO*zyN^ zY%X2;@_Bk-e6x8@jYn<8)cHVSsBhLdt^CFx5jE3Mp-Q6!0^m zv~KaAm$xrJSQ}bhY4T+6nXn^&a!;SUIp|HK6m#dCi`?avhZQG3$xNGe(s=KY2PfB0 zID9kY*tGkLZI&*WZCrAqlVk1kxP;@+ETc|u&yCv6@nP%nyrE$Y&(vwvp(x~Xzd&AYpH zCGV`exi8fx?R@pDG4tiliSJGRsXcc0blkgcZlCVk#Y?4gZP(f=f0_R!;Lxjg3wA!9 zwtkIXVc6ol?;pP$#02ytJophZEo6P|=WyP{>-$Qgk zUtz}Cb$d;G$c1YbS(RH(ykGm^?U}w{*~@HOl{a&q`nqJ^3Tg# z+w-0zJXU3S=X-8dckc?bMYFrsieCQk^P@uj;f;R}*2{b9m)dT6UGPF_((P_m)o=f6ZF^hxdhzvDzhi}+Pug6Si#)OSX!_nhr>6Dt?ELKFA6271 zR;={9todcpm-@ZeeU-h-OwE_yy`(RurrJJMp!q>B5$?mwuzQXTy+ee=yXC*SY> zw>R*{i`C1mie2X=9p8SCH7{((w!)9syW4+kT&nr_Uc$bfTGlJS4A-PpKh}GeyXJZ3 zN1OD%wcibQt^87bCnom!$4tLlZ(0tSn0HK=6ZK@7U+fcWm+-rGFbr);qiwSAJTQw`YgwdAp-eCeO2T5q|gH(JJ)VI-}lj*`0p8OV!RC zc(cBymO1&6W!4-5uZDKc2I2M zm8Hk7OkJv_S@%7tz<6V7|KxVgh@I?`uJ`WES{(0W^Z0Ttr{(Ucrmv-rb)KmdE3J{~ zxVyRP-kjHxwkq@ehEH04%|4}V{MQ)vX!fJbGPRddTOTf3 z-zc~I)3vaL(-%EH9lrQ#=!#=s7U$mTS*J2(sdUlY;~XciuGl@f&~J`^<;;6hKOVgA z(|Yt}*YWI!>?h`WIvmNmQx+M&syQpsWX)G6ZKc>V?b*>YFJ0Ss(?z3rqLep>Ql@cd zi^=7peC<+sb-#_g_96YX{VY>bqkL6w#;)=#3+w-JWl@y&_47`>;&Zmv^yNR%UhQ*r zldAUgH6IEe$L#pF^O#`a)R~@F&mQj6-L*Q)&-cmB_ciAa9n(H@@8X`Vc^?*hZ;x4H z9`rlxlhdfSu|gNpD%pH zVHH!t43>YDSe62JN=k?UPn?|32@hP4PO5%h zu|49PaBpyrom8FT&-AwA*JnMvn`>UXeTU?69sP{dH{~Ce&pi3@=5+tf6RQiAdCtqu zHcH893l8Gmk(s~Z!giU!ZIg6@r|o?8tb1nlo-OYl-+y@H`C(7BKd(NVT6^_+dD`8! zgQcf0y7RBRvwhhbn-?!{9h>&5-SD#pMYgXROuv6c)jn4B-obh{iYWJLak!43!ZyecgxR!-DsDSC5`ur>DEBt7~?JuK9+#ExW5jbK|pYFVyC2bK~7myYS0@hOE82 zze`TO`lRkkb>@q(xNE;t-{u#;lT-9J zSIAmfZLhGtd2Bv4&rjcf^v?h4&ftCD_doVE*B0LU;Jx+wnj3THeQxfOsgbFPktukz z)M9benggGv-{*?We=EHE$Diw*OCRgz&Mlph-IA7h|J7b!siIk#CI9YDWxr-O|8`LG z%h>0Of92GD-+c3Z;PoxRcJYC4vaCwaWF{ZGy*DdxeRZ6is@LV+q06VrrF?q6&~EL? zIWJq+-M*Ih^!DZAw{d|{)!Wvs4p+95)31wp@Mx*_VXgedcM`g$ZrvIjce~TXqGPRU zMqo)$i07w?(+?O4^nRQB(sb9FuiBn1Z(mN+z2oZ})s^`z$I|gi4CnlVaw+;vzjuXw z%#W747FFQWFX*Q+Xa60?wc(SpR7D=`{;og$i^aXGW!3AQztujEyH#z=ulCfow&>p4 z9|v!}pS)e8;A25>=kllzr%TO)uPWU4h?{VF?OfZM`8?m3`$=xw6ngLReVN!RdG5R4 z{eCU-=)LyQV=w))UT=E$qxZ*=*ov-Kvh_=4AMM#|a7}sF#CPKp;@ z{?z^Y=1$gj$6ATfyotrHo@LMcUcC0sv1!)Lhs#7l)<^EXSNQwc*UUSY+groC_C#xZ zT&5&^ar2yGMspUkpLp?EM_qa65u-mzr)v|$Z{0h0V_kOc z`^=*iN|iIO{AY0JxP5l#ijvP(VFt_9uG#)L^+hq#+#|O8eX_){F2kMDmvY0x%ihdt zeLr)D#jSvuQPDGaPQ1I2Z*q@u`MuojzAD+yd8@09mPdQ-ny7K1Ik2)?^_gVQE4g;n zP@ev%`!{*(cfLr!>K$A#Ev#qOhdZbJKd8T%kUnAG&-AQkA?F|O@qN75>es0fzc=5^ zu5VA)-@LZ=vQlBl4O<_H5T285Q)X^eD*4SO6ZChEkxHFs%9US}C2kiU+%DlY?^dqa zo{7=6VfQCY`6+*)@9Uct@z2iO-WfJ!Uvqa}*h7`=h4*(pzqt2f?~X0gPEV~i{km!L z+&4L&Rc`O@*V%b~TUu($(__w)o&?8i+%n(xirJ;uiHetBv@JT8x=ne*O~sClvvQ{e zv`uJHle@Zg_OITXennH3n@4)Sum4b{x>L71@uj-ouk@uwY4elC<&LhF`0?T4*0fto zbl3j2G0NTP<+o|d>?yM+=YC#v#Ond$M1P)p`IkQU*WP;5b8ofLiLLVjo8popm)SR) z23!4oI(ehs$K6@Cx?is;n{}wP^lDOsaaBpGF<;J9MKAjkbI(*N&v^Uk-nvglAy3=O zZns>?xSKU6)$LMA*6gbyb8Yye_G<3Zo_+1!TxYk7>fPxx3yKpb_ZR(VkZR}WS$luW z^Srw^kNVyDaVhM{(xR(dRi@9E_I%`eE<#S}&X)Eq8@Y6jOI*HnpLOY>ug7fXx~^J( z;?kolZ?8pGmWI0)P7HS3wDn|8Y@+OupRrGCB)Xy|?7QrH&+_$*himSK8~#|_w_vfT zR`_AHOSx+I&c08Ib29%D_+nkSugUHivrca=kDTZ7K7MC*@9kBeb#Aw;bGoxs;$UIu zA(Oc$m(21C>NsR(Ucz9M`>^)>o%)8~(YB|`WbN`NXYKjUuGKSJ{(!=zn<-u=R_F*P zcf=lTobaS#ZAgE1)XY@L*e6Fcbt7~=RV7Po9#6EKcU?EjTy@HF9qn&dQWi|E2`p{d zc1k-nW&5APRr{vJa-Dx&uKMuS)88|;RnGT1J@t0*iK!=Um+2jDSGt;eYqzTAj>tJR zr_3|eu4cLE)qNJ*KmX6^Q|8Z1eWxYgy41~o}@5~PDYFW-*oMpB!*Xn5Hosf`}G{KiU4lppJoj>4| zS8?7V)#>OHi=Lpg1!wQvEDUMC+x5?0C{*&Iou}h3Yca#k;je=(zt+1HQRbX!^nQxF z%9l-*-ZN9V^`G<0)joRj(9cHyV5psxJ#+S!OY;7EYT0T9w@zGnw9ah%n#Vepm8-m- zEG*yK8$aivPHE{W?c_PRCx5&8Z2tYQ&ux9chgzwoNrmobDy?UQz5XVddE>>bIl?kU zW})-_GMDq*D^uRr8y&20q4~}B!lyw#m#;-WU+bFiV$znmdhs>(C!qwL?SBeUZbISa2&TrxVH50yAu336*PE7gKyE|^roi1E(X{*gz z`GBn}9(v6bTN?TxySO`cmDkE2^N!8C%=i4C+Xbu1rzG!8KXYyFU1h85EGsU}QJFP$ z_qHdO19xUU>OEmL>rq!w&!RhVCoA;#MO~Pv5#jdraoN|SpT0$;?(Dqs@1dERrkPjl zl-r*kMx@TH+qAV&&RKWUCyAI$u2g^B$)$%<_4nJp{Ofb6C`U8p)^6@Ew-z5(jn`Sq z{$WpaX84nI^Itq)*_^u=+ZRPZwjXbhvtP$=k_c(Y4&)%zrxP&-8&cVT33GT{ffgcHc4Aat;v!N6ZY4dG>unR zrJMcLqNUy3*0U`qDK9#iyUJyzzs(j^maG?(l-e!V$5eewI=%bgeT@%4S9Nvn%Fo^V z`&Cuwi@%pvl=M%}D|$Eoef!dLuXjg(4%+d~%dltuYza5b5BjFADz6_}B!JsZ#Nn6~N5?kzROYm-&qrgOTk zJe0kuU`op5Oxrc*&rSY*;%1s@u-M`$ce0DsH+1-J`*!f6xXyK@tBa7fc>0%X$@e9{iX}xOwvscbw)uXIu(V1mi=E($S=EZpJUbTJ3nMCljyDo9fJII`zgpS&QB8PK9lW*>!W9-RR@$#IpR?n7S;RflWUKw=OOH<# zd7JlPP1~kaJx4x~$WIT#Zk+3uO7!WJ^XYiA(@fiT`OFu7i~cj5mOB54H}BYTRrNI9 zl`GyCOv>_9yObN=sdi~qbl4}w*ZY0;NAK0WYN>Q)P4rbG-9yiGPp|wCeR8wKovoS{ z$AVi@554s(=vlJ%&Rj+NtoQoe;hkl=kL)*=EYn``F?VA7%fHU?_2t>jsbP<=T%W#M z>|d7RsadP;#H*jty0d)Aajj%?)~{#P&))njv@F_FG4J`Dw`XVkI%m4SQuDZ!Vcw)g zc|jjtPX?-P&d|7MF{p;%MiE`R}Pu3nfy56d*JU42(=;=v$ zfe#jiz1^yIJMg%sprD{&iIS3%en9sL7bWM5Dw9H@C;noXEhlfYCA;;`oFm(Pw^;wq z(cHDBS@gyhi`@n(_wFTrzfhgs87+10(xhEJJ0y9yezVQ43+C0m;i(*1dCd2e)u*R- z^X@Gy^n7e_N?ar}TvG4UmTjJH(NT9C=S{3tn;*F=dgjR=MH};DdT&QcOxL&6E_;7# zsOE3 zJl?i)f`V(>e+JQgThG7W>UZ$awltl)!8`SsCtrGc<5%8m<(R8#ubQgGO8d^PndUB+ z?BmnZ`YrVGqDgLUZZ4_8t<%ey^{(ai$^E)=>b#%!b_-Hh-E%7R67yL$t7gtw zlWS>T6;Bp!4qr6sgUl}0O9Cr7-h4l|>@TNN$Cb7!jR~E*yrcO^YaCHdv3G0@4T+8`=?+0^X{J2UXKIcuKjHzn!U05ZfO40(3r*j?`QsS z>)7ab*|X@KXxC$dog0%9ZgrJ-e7bVU&s#8U@9x+4W=bya^>kj{cW92MQ12GWZ{CXi zfhLw(A5y|k=D%q%ufK45RaxGS>eucM*PXg^+IQWtONTS9+aw3%q6NWzd|-U zmss9)Ke9dd_o>s5-;_&vR{Ti1TlTF`UvpZ~@`dh(sZS4nc3YgAthQtA!`nNHib8LT zmi^InyIpwKJnO0Sw_dHiMc=;Ny*Fq5?P=BbCsj__bKG=;bK&a8J8m1S{J1OQ-JZ8) zmc4;Tyi32Bh_?MYyG_bJ^LW_ydC{9>ORpZ0`p;naS+2^LcvHshx zU7IhyoA4#t`Q@|O+5G;;`piU^A6Xoyd1uEZzqc>2kb zN$T}P&O`flq_4X;Q)9!W6MIkoE~*S;obpg*(h1vzC-<)ZB409Z@-NnZ-*>+I+n&G2 zcmH(L{v%db=SJ^-U%gJ>yt{2r!4%u=JS+XQ-jyyj%QxoRTJrE~{<5g~{?pR;$W~q8~vZ*l+D@s3(7W2?pRy&*>h|B(e?R%D?a{bc(LI9 ze};!&&Hp|N{m*dYgXQ1!$F*nWSJ(ecKmSf~ZGG*%{|vk1zdiiVaMAJpe}==a@_(OM z{hwju1N+Dy_lwqF97yR*E-MFEfGygAi{ z-pAe4{#PEaAIO?!aazsK@R(mL&b2@kWo~~TA$a=-| zS~fS^-@8Az+^Wyq&a>n7pIKXD&#K?Z?q9miuDSl=`x#Hq2>Kn}lU=;&&sB}5+TW%b zcURBe^H{Rr=cVnMhf8Le?fzKxWah`}hUn`?Yv%)h(KDv#IXch8F2bauw{7vXxl zFYzy)yzxz9Sl9`#psgkAjo*AxHoDQ5cEw)o#rE~fy(jgv)Lz*ZS}B_PO3vtu`4oAp zuhu@7);agHU435@7m`~ZrfDY`T(r}aJM`wZN4GM>J3^=WvPj&Wce*QM9^&@CrL(K| zot*Mp+oi6_GTtvLtvg+A%ItpOPkWd8X!}?oba}H` z;dp+;{Jzwnwd&#F*LJIIH(y?Q@8O~TU|CMhtKWPVE|ChDQuOT zr+N0;-P@sgn;zZy^uF!)vpTz-vXgK9iFv{%X@2W-QM853ANCZNvZqnvwsqXQNL&0>v~$c_MqmZZl?F5S1tZ# zA6b6e``O<$vb!gp>d8HBs>w3L@7pz($tFU*nX9G;d`Xo$bkBKuucLnap1Ol68((B! zIT;&x;_M~m%=d-!LY_Wa5~m`3`%kLx?{bOM=ls3Xb$;&ci_Nkv?L722um8kjz1^B? z7Vn%sA^qpv;A5YQvxCP z&Y4V=>V7n5dgJL-;SVZ%c(=UBHFy8ZU$QRZO3rJm#XDDKmn~V!Jw0J<^37_gxw}ng z&pom0!?`CRwI7{IpNTuS7v1)GzVqrd z+gQHMuXnCcd3l$S z=4M4qJbrEM$tQ<23lB}5YaNsFXv+4`m4VytiakE1x9JzxE3wM9f9o&ntQPyCGkemG zjYXnIW%AVKm*;(55j$nA*2(Cex0+6dEiv!Ro~@Tle?Qk^HQ&Ck>)y>S7J*lqkt@h>8`Z-tBlMdx-ZJB#wrIotI zwbiV19)I+FV&5_4kJEI$YY}Q|uSTeOJPYi8yd`tf&Xcz`P7F0#RLZ7cXBtmYbOE zA9rZ)%*iX_HocA8S?YFodG0CWB|68}>1~WoEy}Nz@@wDTIOjqEeRFZya+_162E zQDrZWAM{ffkNR-BobOfHd$si*>-zoI-nHx3j6Ppg^5%K;>2NXoeHOb{moL2gq5PWW z(RdoTg44wQgUB5%$ z{JHP-rp7qub@uX&Z(cL*>79^Vc2;n$&gn!wqd%TKLK9|A4Z9#;rE=qx-MbL;yQ#GS zTLso$F6CLPZFX;AyPiyU(5kvqbs|@TV^?lTy7T(vEphu#+wwJvRhL$~pWF2IPJh;& z#}>AGjJKOg-k$d3oZ+N9>DzAKH;P@P{l$Er{Kq1w4Uc=^82;x$oIio@q@%k|8?HOchT8RH(~gl9L?g4b`IyKdeQJ-wwj17CN? zY}hO0qzo-U11zqZ8XeWK#G#X{1B$EQuT+L0Xbea`0R)l9PYbe={W z?(T2feW}Lq#=N&vy;I{pU-p}Am%RB@#jbL*CpPC2j%`_c_ryCn`})HhW2SB|dlfv% zdrwx$l-H^fYKwOMC8sOT->V;b_Ts0O{*)Jzt8Qo;i?&1WtKgTo@v|Sl)HT! zU&qe0onmW6mYCl=bv<%Qdt_@u*S~dEug#yzxhyhrsaUJixpT4St7*2cv#h4hihWy9 zvi?)bx14`w*KhP2{Azw7xccSI$@@=QO&6PYe|EkWpP#k;vd!|Ag?WC53j2QguG*=Y z_MDqMz_7UF8T0iws+B^(7T~~ zj>~KO{aCO3?QiHZ(WAVM;!dGAj~8*zR@d5`Zg%6Sm^a^+FRLUx*Q`78;pNf$WkFjP zJ-v2zQ|a=ksP5gzzSb@A3wxT+WVmUc=RVC}T~oS$Cgr}ozR=8c&+5oIH>=()Ei~18 zbVs(QYWYUL+}+!gUAL;HZC&iEyF+=&!VTKmG0Qc!&Y$@7CvVH`?%S_BH|6T)tj&3{ z+w9z_W2cJOpWU!&!>nKTYQKkVk1Ndznv!K~$oI->>Y3ZU+wMf?ZhLjR`gZNwd6Pb+ zt0orhQ&HrI?!6J+qb%X-CiYNdN+QRSDaj&}JXI!zc(umpwv~Z;@(c_zcf~v>BnzFY zvo1~d*4b?vzjoFiSBok0gPf0?x-|XPr=J0vf_(NovDS^-ofZ?8s&V?ri@@~EU3Dwd zcU;Md+Mj;VVrB5LRom>P7OSjNJ-z1JoLT*ouU|{u7gyNt z!}K%Vp9k!@aedR;H+vQr*=_z2Z6_%C>aXGjW9gks@9gd@{Z=b<|J2Eq{~4CMK2X`G zdnz|_f^cr6&xWIE`ip#5ujq<#F8K50Zg1t8zG>aRws-bNv_3kvy}3N)cFUgZ^mVI0 zyl|^p@3nKa|C)~r^Yu=>-LB;|&%(d;`cXNheEIkbk+SY4M`-$){pH zdT*@T)nwn2ekE4-&h|2!uq8|7xiWvV)K71o-B`Ty+~%^g_gNlm*hhKFtazgdIpUC+ z<&ly39!S`sl3!=*YQ}Cgxw&S~DHD||@z*6wWv(vu+9l6-cc*vfF}*k`t$7Czeq&%@ z@4C>=y=jYT>MLE9ML*iy9A-!N&vY^lndMvYt!1f(l9$kwC3{YK3VG~J(re+}zV*Fj zPqOG?ktsFq6J#0~7+-~yiPeUk_OcK4UFiPNSIshL(w&>v-rStLW}(=hj!(|H+hPS; z6!yBPYU;lb?{3_ujcTx38h|PgtAP0Y$QzlZo)Zpamt7aF{Kl<9b+XQ{sy!Pg% zyD>`lC(2#&3W^nSne>&Tqx+G-gWsO6L7FOcjVC}F85kJkmbxdVU(MOH_-n{5k$RC! z$x}6T&(6&|JJafvVE&1IlZ7059bQ3ki-J7$J6xRGZr`gtX4teO$oohoBg_b}Npe%& z`xd`iCK>o^RVG`#$R*21(_81}9i8^-q1c~}&jh{j!8v#P_1(D~ub;nB)O`K=i_$C0 z+`Vsp_U>@L<*M~Vl;7~+7Y1e}qZg@H`p>yPW4M&nU-+QycK-UZTY}fm-|z~)e*MkF zQkiY{K0h;Pv0dbSVnt;i{{n^;S%os|{*{WRiWyG08=1;yb^0e z@@K_0!;M1a&x_tDN^5zVygL=-bt`k(oLj3h*Y@1iK6t*-QeLoB&Uu5`(X6cRZqK!5 zidFn>^Pbt1XzKF&O2pBo3BS*%{W=tKTs>l;EbruC!RtR&N_%@=zSoYsVDj^6`Z`&6 zwcg{~1mdpg|2Vr{v%*aG#mD%|((9|r!+-9VcYJEy_g*z)?~~lS(aKXN7j?b;p*H#5 zVu@o{r@cNh<(>Q49TV5`_mpcr{`7vn_?I7h`?pyw?%A;PRZifuu*+M&-Hg!|kH7KL z#9i9)d*}SgW}#;-rpu)MC>6Zt?)Tx>gIKp~*V0NhzO}l!Wruk0_49T6&Z`8QJ-)Dg z>2=Hesk&LeTKCDi2TVG>GwPbzoy^|cw{M!u%hi^a1=d{+mUGgwWdf%I27cCU?c9ql zv`$~KWm5MeNeQ)UkDmp!eDhV?leN4nB9Z+n&z9z|LAQkJMLOE`f~s%ZC8u49O_o^x z$m3fd-vI_m!Jj-Gr?1#+uuZ=4U2bx(#QBxS6V6{Xn{E7K*~IfAdvsov>6nLegrDe7 z3Ya1e$`XMpu%ub$X4r+AH03xmx2(7_?ZQ;~xt>m*?yqNx2g_XD>h?`u>+X)zU4~x% z+q#y|c<_sXf%%}WNwcZq7FGA>K><@{{(9!(mOSnDjeCb5nY}%F&Cqi{llzPZzZn>q zFRl1^r8eZSmVKzN!uemCG9Nwja$c9^>|F=SEhq1O!@&8K_n&0EX7gT-QvUhdB zZH}Y%LLT4v_%}d8h{00h%68$Xjz3njHFNwo`Ra%-c9`-zI8SU^Sl5g`lS>!XPI^qb zIhn)MD&>*?<~QBz@8rfz@-v+57o*krwvP2-b$PyK-t)wWIdAXp%@WtW^*(K>#d7Ja z)q5u8ZEQ`}x4mR7cem%Z@mIhaxGSu%jiMRSGvmv>FKrhs?}b@Kp7?2S`eMN+>pyp92L)}d`Xk)CT5Q==b#9xW zxhIeL3GdX?Cl-=gNR6l6zik)k%E`QF|I(^(yxBI8z&t<#0|1&K3&*1&; zxYTpY=eL)>b@CEh9d%sl%zTmUm!_}RyBZbQGkM9jhmM*mOD4HqnB?LTw@|P|k^Q}G zpr@@RXQ2FoA_n<3mqjlYDJy@TI8jpmp+w%D zf=iQ@ObvXcaq+~j$vhK(ExE+;Pd}yjs6MyV)o#tJPNtuf?>_q^KB1jGS94yde0Rib`hF}h$p6%=j6FZzD{p&e<&kS?^=`59^1snaJ!e<0U;edtj(6GeEBk*f z*r&1M=GD({-aLNP-{iHQzy7enUiW2x7OuGFFB-Aych1S%{~27v?*C%_u;5wAk#Ewc z3|5@~x%*^^nBUQtufA!&k)D3*^@pe8jzwF~1l_H>RcEoQMm;{|;8(uxo2ApH&(`>% zT*eju^5F4jl9ig4p(}NFbI!JVELRxIy}2;pcZaI{f%hw~X59+D{wr0=d-?nYJEE@} z=vAj{*h~H6nh{s-Rk>Se-O=5ahVd8gc>cO>z5SKe^t|~guYdnL82qf_%yaR}n`L*; z(47ACb7|Y_v#0&z+nPkbeKySd%lAb3)2d&$&$t^;+u7{De!sz{+@oc?iyr<=nNuej zHF4rZNl8gb!HE;UDl31QIC1)f6DLBZxvkvz-rfP%?bo1~pakbleAszfkN;(zP$qBT z#G=g(4<&PbMW-H1b>Hx9nezH~4^t(}JS!(h9f;w%b*;^1^XjtW7pqUaD4lm$!zilL zGiY1z#I3&k;?oK@hK06P8RlgL_e4#3^5k+*=9{%P#_-TE*29L2Zx zuHIdoaoO;+?CQ3?Z|AYE%3P{isPi_%$i-~cne(PICDZOktqxb(CAj99wZfgm6`SmK zPM`DPqv6v2Wih>HR;tapcH>~#mhWD1T%o#e-*{gB;(T%TjGx^x8&`k6CVR)1>&#<~ z$1#59pO(!{=Dhvw+M4CtUeD@zwy&Jy=cS#zJ{!4I1Fs3CXxy2v>#ml4I&kgLJrBQ4 z$&Tu|vQ=_^u(7eR;kuhY{k4A2KeqPx=0D39x;tK4*mo)X$!e_{)mNesi;N0Li5lh{ zepWrtg6~d+c9-RD)=ao;bvik9w!QWlBQM+88w&5%s>y^sypSvsxY*-NdQfnK2BeQ%v0(pAN#Dogy%3Jo&78T))<+?+eJ zubj(#b#|`5-$hfMb&m)zEcYLGuhdDde-QRV;WVy!4m|YKkT&Z2OXtD1-eSv(L ze};vZqvHcUu1-pn>3X92Mm)P<$E<(1)Kb?O{G9x_B;DXlu~m5LW3vq}J@0+pHS1@Q zn(vgBgC|9u+Pbgc@|HiDI@%NV&$vGO(2vFPkG;H8#ZN2``<4@$#r!gTalKV> z-`1>}i|tdFe!8PJ_j(oIHPrT9r%EZ+R1;w4D9RxAV7d4?CY-m4EWbp5+UpjvU`AZT2`zV0)hR+S@H5iLAFbs_xHzdn+XAf(K#? z4&lfXI@}v|TkF)DZpEt4iVF|DQkkf;>g}3UclOP>?>e_6T=7JrN88CSmU>HmeX)DX ze#om2MEG)w>S zlO=OgzI|~0y?mpp&g3tjN4X$K%rj~VujW%o2s(c$><03zH^~(>KA%-Y{{w;o4NYp^V`AM#cypxE4Ip7{XQWO zm3hA5(cW37pM|W~{GYcTD`CBP11UwT%Gx3WpJ0X z0^Bwt7&~rlj*G~-bBN87=T4IRwA8Cj!MC(tO%v(&tUu*8x%P5YRI2)u;MM-|$yZLT zXBVp6$#tg8y}FL|_8;zJ$#brriTJZ|@trH{gTnW&-BC05_}lP_xl{HC`pwQr{nR&U zz4VIpi2iWWT0cWA;vi=N*1M0jh^DKT1jY;Q@A zJY#pdVUz2>m^M8fku6`UY*@EtEnjbJ*X3y{dhchZk*ezTE0cc)N$%_Ke4i6)sXy<| ztv_?Suibu{w(6_?jHm00Sw7~R+EpiA_V7Wm|MSUTQ+5R3JuGtVn@3}r@&bmT8aO*< z%@+2{-)I<9cA>{)bO`hD%*U$2toroOy)t^DqMr~F-Szgex{e9O{&+OOh&ikEa>?fdht z`nc-hyM>qA{OoS;?alUj7h)2})zfuknKtcQ?mfp;sb#AaE%tS~FF7~umeu?TyS~cq z_@?(X#aZgq+SikNlJ*!)_SBD^_BJ`};DwT&wZ*~n%a=V&j@@4tvYvOf!PL`}mgg#q zy~s`Ve_vM8f9>XqO12-jTDRX_nZ57C%124+YZu0?T^91awDs)q)9Ra7_Wft5X!*LY zC zTdmBto9^|jSTZYnd*0pU%VTYy&y`+jGjsXpZ4FO^Uma;0lEN#33Imf9B= z?3y3xrvGPuez#h={INsxcK>H+oX8$&EW4IlnoDo)H{bYPC;s&H8v6rIJUHE(&%nUU za@Qr*xw>M;D-ju=QZ7CWshmu5{U5r#|ml@^aOqyVi`m?Hzq#OlkEstqg zs*{Z7#Xb2OKQqyKSAD6pz;x~@PhF$Jrg#P2_3vGFUz4e{`;z-|S%H(sFaO;8Jof6Q zHP+YOB;9zg_IBltLd!dIXKQWhndJ9geAaDGi|zi!hugz!B<@t)o4N0XXf)fW&ru;c znPH|TlQI@MdoM4qkFkz_GkhcA2Z%MAnIFC@JzvEx(wpmEr{{XcXLe;-tbWv-boo-TSwAwiX76$n zH+(U3&fb+rPy3!fy1Tzr-R*7scI|SZ>Wn)omr8Erd|6+!dqKtqvFTF_4_Df5zr8Z$ z?(vULADq4>CcZLr%ASs!ciyboH0##%d+$Z2YRgRdtodfirH5YwZur&A3q#?TrcH|%=5j{Gu|a8y^0WidhhG^@+IrPzq=aGJ$1&dvb#rC zhOA$)J<3x#LU!sNGjX-;cDJ9Ho$BP=R z6TMFQ*2UY;)s!^XTf2RaUHYFS?$kc}v}jKrY2w_8}Ur0&nG4i8ytX^=Y1-Y|Sr)c*m#>EFk1NX_&HA|9N$NSjhh0~ZoX>~K z`7^$}w){3bcynIxslB<&Cik9>*s!-qXmwo5qNQHD0?pR9wl~l3lugkLe)4fsa*s=D z{;s=q7cP}u4r>t)_KCW+cB0X-?BgG#Q;8nVe)IjJxQ)xRLi$A(C;d?`6ZCc4uCx5!ty|YGD$3`| zoz%Mh$C&f%HaqX=w(K*`8t03-=lgt{FMQbWcg$kdt>1RBtd8c-&RFrtZ_C^lrUmhn zzL#9uz2eQ4vr!#C~OU#lNvrBq{^E%C5Y*V>8;dRu#&eK(? zce7_~HY_lywy}NsXZE%?k=u`3+-{e>y{Sj}sHL0Wqe+kEEEWyBq<{X`#A5%y6Mj1X zXP6)z|80q0{lj%h|K6Yd)q4Ng4Efi)7wvyrul=9l-VXb8`B$!I?Y}uc<^Si9TYqap zecR6N-}}v9iPz7vkG(7UpCS8a{J*aUe_Q-#2-3^^&#*~eqy7_f_P;C5e@xHB-~G8i zVE3PkzuvuA|2zKFe}?bt_-~${R?cJD8_AVAb-M6Q{rK2;F_)L0pJ((g77~#Nl#1yL zR`Z(Jwe3j8#u>TZg-2IvI0>btxws{F7Ckp`(kni2Ccta$Zu20|{M--ktaZLFUR-Iu zu)qK8oXy2bPo^YGyqaM&WvRNyF~jiNMY&5R`EGmBaq*UYNaTu6BWTX@^ff%ZFX)q$ zahX-N%bS{Kz3=|&JT)+UX<%?Xw%c;n%fRTfdVAL#6*n{4ep1gMnD>b)pUx$Z%%e)5 zmlwU;y4tpcj|h5(EgP(f_38b_|WFDUz?y^uTaH8z1Sleemj-GtDOEq?S&h}TTMe9#&n^-#E zr{C@L_vAF0)1sCqT?-Rl74$|gdNgIj6i^E~&^tR*Yr^Hi!tM1Rzac*0N zS0chcwfMGhan>xK9i=kW z+p=EFS#%|OX~xpGap##!Zq2)La{A<|sh8g^j4a=&9dY@qy2MT|v3#fSL{U9H?P*!- zqy3`io^bWFIAz+i=WxD}!lXhP=xABCq_e-q!ftJ>wwnRng0r#vdkniF+! zkMC8dDKml=ZJZWi$}KZ7;ml$anG~;(z7>&^tjsq1KK6h5H#@XnZhm*w+qL_Ox*y(n z@#ne6_PA5m>(2J{{ye_=ukOpg@2iUY_AX0EzovO}?u$LGN98n5K0bKrOTy#ry$79h zYTjF!PD_1o_EFFxRdMO_3nxmJ-u@-3e&O``sam&Gmdrn7^vdbyuiel2cl>8~nNvHV z^Zv5*#X%+i8HDQp+K2o*68X0E;ns71*IV*MfYt^&3z}u(b1oUXM8$QQhJ>4p>T*f+QYR=aF zE2m>ZReiUrdHY%4_Eea%RHg6v@{C>U*4(;W`0k$P#T`dKEcd#2`qpy4V^@8I=lq&| z`P{tdo}afPFMqjtPAg!0)tAthM^CqR-EF!1dg7y1MJ+0e-EMoDElaNBl~d*0c6;UH zqLX@xdMa~n&r+PusqLxJc`WYci_)bN<)>9Iy$ zNykq<+O95FRpjgW_PEdF+I_dGJ@hVB-7uBq4!@M~RVI7ef`67rKm4?qnj9~h<-KR- z)w)@woTu)qzOE`;I9cl2)zuf@Pl~RX+gWfg@V@7cf?ZbT?jHXcZrO)Kt?1wn?|}*qng(I_C-=^@b)# zty;Bq>Ca0t(ofGTFEB4$_QkAndh+`{cPegrUs(89qG$V@_q!H}#m?|uaBSI}x+lLM zX9bqxAxT_EEW&YTn7xhz1tUZ~_41P3oD>_q zqU#$BO*xi5P4o)?q%_rbZ%@VDy;$|_1)bvr?ZR~m+tIb5!rYC@!caW+7DtvYdabuw=vIqZGF8kx4-4-t^W*a ziPN6g_odyqdQ7wGOnKPrT)VffZ#2YsH$Cpr)ePL`X{zYrex)brro~K)@amq^;kt24 zE7HX!t1fv?{?>j*X8)NR+#yf*ys>rmDJl%#xITJ+=1G-X;ksT0xoWdRSNT?Wxo@dk zv3!v}O4Cx%z~nij<;?xr32|BbY;wHcR9)S%{>qbUr56p(gxxc_7FTC-YR+Pd*}ady zO%S`xmiDJo&!{Y1q0UboAWcBb7~I=Sx7qDxcqB9HdI-u7(vx5+IjFK+p|h3pgi#~-I6 zGWC~I%VFYPJ6 zYR_JK^jyc>^})_8|7+pWb&CvY;(hGh-kkn;Z1b6IS5JoZRaEt^c;owa?i~*&Yq4{h zTDw%kE)}?^ysBNi+=#uO{quZBdNwtgb zF?@gj!E*6(PXD&8ODlqxX1t93QgSEh&&hLtrk>n;{>S0)OJCO4eEe?r=k&(zd*5qp zpO@di(=N|0WAP*V@NqenJUE-<|ws$vow8R-axFS?Ew|Z6ap3BnxwZchnS8jS4zx%jG z&T7f+y;2C*4Yr zywStsbt&gmi?Vb)*6dabS!pGnxlzufOkjC@?f1J!g)ZxCy5#E})iY_z#F_IQ#qFY> zaV*zNNL#V?{f^y#F4w;Ox_YtsI=8Fqa-Vj_PWCd4TE?Bb-DOhvtJ~E=(b3BK8Gi%Q z7OA{a`u<2J?a8!5MRgcDNEx+HLk@Li4i*S~#)=k4X zdG4u?-^Jv5#yfjXjM?}lHeT=0$)iV3?(TX0{Lq!Bva{vqEV_1V+uIunzm5l2WuLya zW{Yn9j1n8!JQGu+!*~LAz)q1w`%+DP+q+FLizqh~0Y{B*|JC5e--z=n10(oChOnq*}b#A-2S#)=BujQyWW$hLLPc*T|KpG z%G#_Iepc?5Me}x_y7;_SJK*Te<+x){`HGJ5OVQHkZ%$!TFXU8cq-$7{Jreo%KWQ@LTBWVPed z!ZY29y!)j>(r*XJXtQI(RbdFwf zOw$x~v89r3UK=5eMqiU#nOAe{;x7JL{H){cBK@|xkHZyqvZ~(r74DZ~JLyWM-l4gf z-H*kJH@;fgb1<}PQOuLOhb!LdOji=r-1hWTYTn8WCrp6e|u`M!MH)pMoSO5U0kHBHIAx0mDIoR}RJYj3RHTci~& zy*pBayj#_;)kG*ZSDE!I2^3WmBrID{syKmQt5&W776YZZwkNn;`01;vg^ME zoH}-<l{{QQk_ZNDT^k{^}mGwbUWZ0*iE=knb0q?PB;lkX&-Njz9OTZ0v_uOpqs;QM5cHG^d6m(E+dSZU}qUp(D;YP28RH|a9 zye-U~=C;1+bm0`g6O|sRQ)dZ0nzUWoYvt#i)-y|9=e-M)y_%a}xb(=L+}lNqZf@P% zom3sAFSGKc>Vojo7a)U3BKu=3ePZU4DVhdo47#%*u6Jt2W!Nck+_*uvy=xzuv#1tZn6Y_qY8! z->>syociG2{c8UgcDJj&-_2XL{*`*7k@44>No`phrdTGR-W>0?h8wk5SK20m)~)B+vh7c{S=p% z$$9rhZ2JDbuaa< zp45HYx7T&~_O#=B4w%-hF4;c)tMo3ry=Qi@@vTo>T6D~9VQ1RcZI6~rc^k7=^5Bk> zo|>*_wqssqUwo3JugFrz-N!a{Rdew)&B_v*x=MDf>6(mbrk^IY zE;+k>w$q88{^e5w;*am`EekVBv@U=2;<3tOl|Q^zCV!U2FMTyDZ0GV@SGUB>sX43l zr(*5BjYZ8VluB?Sz^&DG%3x^P2cQi^$c}Z>j=wr zcP3@7Hjm1Dm$~At|Hg^a;v;q$_fE9&d6)IHyRvM#hM%~pjl?AGsU*fx`^QrXGTfiK?5ud-NV zodFq=cG+`z5682xKkHkISN(Xu@%_i&W(!WNI{D_+jKhW2727l>Jn@|JGcI3S=D6-c zO~H`tI-BKD*<1QPdcKc-d;iP7&}XtiT+jGaUq)q`s_js^eCNut$70*^eV1<9eUblj ze|(kU2}|Ka&*mjv&dCy6YH??CUcTDjosLU2?riLqzMgy1Qp>HNXQ^kw^21v3-u3M3 zg}A(aiWOU2P5F{>_j<)$w-cAkzj3{vpJ!z&x2QWQGV1h;$4j!`&5SQ=GF`n$XsPJA zTDxbj3yY^oFZvL(RsFSg@BTmuGyP@qi_21fJdS)8o%Lk3yN#{el`^$&=iaF(%66xV zOpO&wFX2}Ls z8$I0dCtYaEN}_+ii+*~h%Z@#|cT!(!)d!=(r*WlE-c=s> zlYa8nJig^EQ(~6(R*6?wTo*|$GnhPe<<_X%I~JN;4(iHWnq%$$s!K~*13E__?jG{e z_TZlP=Or%hx@>kcymytRcWZm{w(!=7JJ-X`WR?dlbT1N>t?n~hw<*oyNXaCTtVkiB zQ`^~n&R===-Kuu>^mRF_TB4VhTbgdtK6We4SoGPs@+a?H4*bbH7d208X491&sS~Tj z?%W9~>RoYiW$Y~9%4v@^cP&|T+vQo~gU63Nleu(${K=ViTUa8`*<=2elBqFQ%wBnB z`mB0wJ6Y%u_Y?OwvnrR}ar`MV$zI{);>-8)=Y&j?UL3nUXZdUE&h;V9`M;8XnI+$w z+CAsAxP|Ak$IUDKOyiX;5>E-mu}hl#mey4}_2K^WuqB;FhfN-OwX4g1X5+~_XEW(lN`&tazV&G8k!@l7 zt&46xzI!ujX1Z>bXNmgke0O=bXSK%ng?p2wKhKj{`ds6~if2xTi*w&>n=TzXsk@$dX;Sb?>?A*mv!(a~o1C&u_82Q&qmz z?r!yrm0P}n3gsui%&!`*3`Jebb!SJF0e4*QP%7o4R;u z`NhkHJ)hOBKZUD(w|~qV?|o_S=i_&Nf7UyuUw!w(_qq4$i)3nK3LZaP&o5K4^2q9v zdemm6V)Q?$XZr8{++Pv;BujV;-G3)ysNky7g7bdy4&A&9bI+ z6x`W)LPPuYM6IYxDVNh8>K;~Bnb=jcRy_RB*89&MrQF;wD<*iWh1>d~HH)UkCsth8 zHS=;_<`uEYr)>@{d}Xy}v8J}g&L2C)W0F>DYL~4$muDEA^3rAdG|&v&wu_QS1qRpb z+J{Rwbe~9;m@_-3tUctl>Q4DFU)#^sQtP*dET0>E;=cEK zueIfo*FXJAyu9Z0j5%A|EpMDTxzSvD=Je3SvNL(E%VnbTyY5VpKmTOuk-O_eK9!!X z7wVHKnEGSN@8`J(R-e_8=rrQnarA-T88haspF=*nZJF5@xK;I)*7VD=m$O1AJv?4> zJ18o;uf*|0)U@48U+TJ>z3RQbOKa0q8T%}zYEW03HAQa=kCeav&T69n*TL5 zo&7FytCJslD;+(nt@dcC?(My`exaFG);q4PedFKx(a&0VX{yV+$%uP zn`OV*1J`9o#ii?|#va|7_eM3pX^QW1dsA~xtySUOS#RFz+G@RQxBGNI+C+x8oLk@j z#Kze&)9o}58Mo=5UoKrKrt(xps7?xyY+*{fFgyOwTnvE9<%&y&O#ZrtH(=RMwqJe#+qggxTvyojUU&l^2E%oXO9BE^icxL?n9-_QRHi+BGyd_1zs^y@mI!fD;l)!qN?Z2Y_8x3}Bw3+GmRIybdE`udmG6}w_Pu9d6LY<7L%sWV;E z%RePiRP*FTSF=m1%QZjq6f-{d`*X(V^r<)b@{6xnbNT$99m&znrMSEM=FyI+7W=2y z#k}M-I_;@y@%f$N7FF+{pmJTOvb7K5=02OcG%Ga!pj)EN?B2Q4&+baIo6ULW{IuFW zj-#PFwI^1KcTLSN^4z{rHe2G(N#U0Y-c$B{{616FVqZ&a=&LV^D=Y(gcYkUzb2a|b zy&yI2>?NHiPwccWw?>=AeJK5~`AFF_d-ka{laJjz@u5?A$<~#oy;qWB)xzJVOka5J zY_Zh$+bYq?Ya=#n(|&#DwN%#4!kZjL_9gA9b@M;@+0`yxmo4!4c>eRSCwp!NC(pT_ zlfB9C&Wqctmezdza`elxYnJ|jJ1@4gCpuMG) z-+nCS&NH{TQ@sDwe1Sgc0>6FJ=2yIxvT=GABB61tv`;(KT=lHXGXFdk&A< z&KdR^A8$R6n{w&X?%wPsj|Dw<%Hm5`>boB=JNnIUS-S3$Z5Pd>9=B`{%2VAk=}J$X z%-@Blp0iB-as0`gWdBxk-F^O=22ZLKOXIFdpW1Az_4mr;cMGdN&0ZbqdFNQ`s$*MA zcT5$2n0wLYaDudYZ_I;BeorJ!pYiWAnODgdYCBuc+4AZot1UOrEIwJd`$VDM#?voy zPvqt~ZRi#{q`7wM>pdE1+QRPX7L?&>uS`J=gftG%%2BJNX%pXW_44iWu2 zb4hU=?@xYB2?_2XXb~W?bt38{p{nk9iO-B)VF=Lld0Nk z^{Hrk>^ke37v_6ETn*g&F>CFvdAF)`zCMc0d*WUl`R?(#TPcszO&tr4y%)*O)-v>T z@0+rIX8aIsg| zTJM~YYIW0He$kJ`Ll(Qex~u$j{nF!0*KO`zoAPD*(wXIkx{pqUZRL6G-}Ys_+LezR z*Xdhb$+b88`oiMQ)T`PBh1=P+Z_av}-l5K4p&RSTUh`I~#_8TUWzA!z*UUo`CqLV) zEvcN?8TgSWPUL0CjNci3TTIQ062n*K%+}qt++((-bHKHdy>fy1ElvcTPX+ihYQ+DcdQk%sm z)P5FcN{e)9dbjcE+!{~g869u$>@8k>XLYmeOODjjp1$FEE5CC$if)x&r`pl)X?7{4 zEo@(hO=u;~9sl4g;WT%`l!&{>qtJe}IKl&_c+wyEH<801= z^{*DX&SY}_B&A}{`P1iX!Gxu%N7izj*m5@C=j!YC+;fV%R)=IReYn^=y*E@~zs;A; z^Qwxq^Jf>#v+X`N?VaZBoq4%hJ61XcEtx8ttSVV{sn@(szGlMJ&@|&Cr^7O{_U_(w zw#?UL_mP8L-L2Q%7aUVHWj(F3C0Mql{lpYs&9s{u9Vd2md|q{9+PZ|~CxO}*=WM$e zx@_91_sh;6dtN{NxWOxln!9T=Ry{qLx&32~_wM59Ea}pdW3ogaK3X!XXldrAW$T>S zZHtXFAM!q28hv`rq|3$cRtWbyX8$f*_3)zE>ks^y&vF!JZpxTy+PYP@C#XsLtlrsU zBDW{WPPkJxM_H3A%l(O*U)$cQsJ=z>AMFlW)c5XN^{sXPB;Gt)_U)2?tx5N#@0a4W zmsFLm^LjFS+JA=Qw_2xL-d!|(PvPBU=`OvE<&VB6Q^Ah&kefs-)a`DuZ z7w_WtmxY8@TdecXzOm{>kMDnmFJ5)>%O{HpSCp&ITWM|L@ICL^)FI>I%cKDC%w>y&k^|wD-{we#B+KDQq(zpt_ zKRsXe{ci5w`F_3i)Sp~y%F{c4RNv$0>Dm6m&gTZ6mTP4@V8_PB6!O8-J%lf$Qs*UrmK z+`6(;@MbSZ@qY%@=!^Fs_i6_F%~@$t@2W1|8h_Vp)9uyxwf;r@OV`;~R{UGL ztFh33uHl-Uk5jGGXQ$oz{IE19?$=g%%X8Y#zfGQVX=h&X?#6@S(w`U@7#Qd3S3mu; zZFB$D-Il)4rDE zR~5~hz9#EiY0Qt6-_7n+NPSMvYH_us>{8+MjPZ<>1Lb*`HqXUwEIIr7SX3T&7GQ0(-$8PwS0A^G$Z-2xUkyh-Z!TwHSavKW5*9=?(Mhc zW=rlanOm`A)~&)jB_CgW`2EB#JhVk$GIusKT7?HTXrvNcE^sE$~w)7 z&m}^o!}L4~mx*R4GjIFNmGevY#F76D=h!VXD!+?PwT#-W?y^p0^`9kAMPpB`&AoYR zb@17HJ$u8R%1Fqej2eAb@z()!zJ z_VLqN_2*Ih^R`ThS`{3!Gk2eLaLc9i`16V-)54GQ-rlsOEGu?RdrZn_jd`p$p%?_qF`b;Qp#!CG*ewg>Md@+pz4xeUUYHLwj!0dprUZK+mZJtV)}cN#qMogeLEP@Y4p>tx%V|bYvo4o@=0>jyuu`)VY~m?mv}ep5(N3 zTkXbt32%>zd(mA@zN*Wv)b6{Usq}YHl}wE<@2`3^S151weX$(JcZap@F(Y$@>y*br zyA79r=XCZLnmMWMrppwgds{xI>)d-f*Zbbtoe%HCgf08N9LiH*#+Bq;!=-H+B~;c>`r2N+s2()YftakGkNMhwWn;$vw~)Czvov{q5HYM z`;LC+$(MJg-Mk;2vGe)0_&baDJ^Ch>{C54$;9E~?-x$dFYZmb@kn`sL;%R*E`+tUs z-+uFz6|I~3dRJJ#&FQMC_uiH7UdL}%w4S~C=-Xti;LiM|*_(Fz)_-~V(%$~7zVx)s zmg>y+cic*UEMK$lKZDfy^7s7}<<|Q5wmd(6=gu#w>HPe24@X?jjMFcc@BMTBXpzqC z-e_siuBA$*!knh_ciPI^f3bdgy(n6ZePQ~IeY%O-Yipj(c^{E^taf_(_FKwIb4>mQ zvxjcgz9IZx_gF}NTi|W4%jZ1brBC_u_{5y+CoHdRxtDwN`?B2Ev2T>`-P|2f=w%bS zx#&swT(K3msrjG{Ov~X{xe8y-MnjA zWBS{9oQ)}8-j;uh_;T#`)Du1}-))zgc}9t>%K2OEdFj1Q&Feklb3!IPoU_gBYPQ7I zy>_>zuM2o7AMo?D&%Dj2X4UWea=ZL#TJZaiYlEI7bDpv>UmrbrNuJ|X_x4=jRhcel zULSWqbnZUKnXFYi-Cv4Lt61k8A1(h{^~AF3pP#eT+}h4pD+g|sUH8p-rGI9Ym+4t2bKm7Xy?Jwvm}_)*yz}l`dk@Zjxvy_-#?dQ_S>9zzqyNFKYtPDBz16lp4lf=j-0Oay)rvz*4w*po;vBr zdN1q9eez9_LVGN~yb5tyvRdIb%aWOb6DPj;v!y18(KW1`ymQ_+IOK)>__nq49bM-;yEPmBTcTY_2Q7wBid#cT|*?DG< zrp79*+?caA<>k$UXaA~RJzw%~cjMw)t$$Wqm5Hu!J2fjT(c*Sb{pJ6JX-l-$g zPP^m}DjNy6RGyypT-4&pHQk(_cke%w3wcsyT)JQE&&KyJ=9n*daecpmtklOf%l4*K z*}dm)RsS9GpW$=gllG5xcKeUX?ELQjSN8rhS^M4jQT03PUsuTA>MNJ{xa-4-vqf4y z?)*<;_XSRQGOgfc;h&=B{ynp{?cTk8L#5z?Ygey1UiLpT*DCs*kaXr2&8)yLO7dkt zH#_VtjM`dz>d9Q8uy@;TzwvHZdiG1zt9P?Ii|qq0t-Wz*GrQ%Td3p0CoHRpE1yy>g zO#60o%FOSgrJ=(4ZJWH-52>_JCkobdYC0xSn}34@F&mtAI?8A zt`)sj+XkCfrj7iOlGWhH|n{Qx&G3+V%aNgg<2Myq(i$W-f_MpTe;({ zuC07v-feo+M{(EyOvKZU4B<{xrCeElSQxdcDudb7hK4E_;o~pc1~J_-PA)5 z4#cK0uHX2o|L@vNsXe#yZ|xC|d1Rmxb-G>jr?2Ak^nK0R11u?Zmke8q`f}s9^<4kGseA1Pqhsfk*DsuUPbAYi z$t?D5Oxd-(wQ^UhGPg}uIUe*&;p6r5x1@I7z4V`fH}mLWsmj{@qV|*H1&yP=IKSF@ zN56LUtf0LIp5FO>eeS8Haa_kGZ$7G?{Jui#+gYux)8?5M9qy@la&22=*o1S2Z|8Pt zZd!KcW3|=shjABd6ArH|>$W)fDIzde!Q7B{PurD0r3W`Sd&i`w7{zWrSx_o#9eJ$W zP+fXk+)1_V;hO@h%YIyyPYvWK4p%E)?rXXu@iU+4U;B{eaNU@mZQiZR)puM1Td16zY@HntD(g1sPMMj3?Kz{xp>=z2OjKTV&d)j`-(vaf zDbtMw zi?6+R&i7dBzUr3hKdgUjLZx?oG{e6_$S99oR3DEa~;+X@Zsc>+Z}&?K00r{^6i?6pPLq^l-+nG^v=H}HvIh2&u5B^`+wO~ezVwn+Hl!^ zf7PQ3v*!z6N|@cP*88Yp%B><^dsYVD_LqAv$^ZGh&(7P+EzLgu z+|KsQM(;1*qpY%ZfuSqzbQeB~-B2<2PORN7Wfs`G_R-XoPn@h)j5fdTF2AW+T)^{n(rm&Ohf_zvi~(`}D%a>h^oFW%gDykAeY;r_DoKf|4P_%L;h%XW<{u1K?@#!WTu?IqWW@dI+do_CvaQ;G z)vv3oIQvKK;kDP3{ii0+c~ky$=RK=vaf^(p7rJklPrA8~^}=4430HmduIOe>Uzi(Y z`(*F5x)!U@xsz9X5}P@3x}NsFPn%Xh3R~#hEjG#7?B<8w56cb~)^H0wWmf@BD;_$d zc<|)cY0Oi?x0|&y98Xy~bBm?|D?kRE1^l+O~iCWxhFSF-M;6u{?DQ z-b37$SjM;UX!F{g1t#xL`g-m*2|7`)yC?jISxx#=o4H||TF~OmncTIpSF>C1 z8f;QF_uY19^UddXx!(LxzaJeZ>1z6~WyzoFeX^X!L0{ZnUE|X}dGt^8?4te7o2#Cr zeKU`G60tFFs+VQKy&rcYQoP`qvvB)o^!mKWqN0=`m7InmpqqCYUOXQx-*^kbaC9RJC8N|^oxCso$Cq;lRmfI znRIST>6)ElS4G?Tl@4sO^hjL(ske4Q`MJl}-fuacy2ZcmNu}uLPL16CISTvlF5mS= z@-@G4hIwUq*v{o=cSe4@_T--xe{r4ZHvO_IFE_RCxy$+L+5Bts<|Y;Q*(jz5mu{Y( zWSVs@bXWPr?rGOKmrHN^c5-#O!jd~zx;+<}sy_R9=huGU0o zXsYhx$G1ZRwo9!zBpC9xbn=vW%EtaRGatV0i9fmg_{M)8Z+<=Fbh{Vc^jarQlx4F| zC(oqh<2Phkl=SiCMpTRG%o?}MI zewV)0uPjz8{l09dcl||q{+%h;j=Z{O+x_ijxbJl7Rn?kqPty86p8oLQ?q|i2d9SMd zI zLZ9a4=Iz)~>!+U``fkruH}y07`eb9D&hYwsSFdu$#@YRHDpS9`jPi^MIq`5(sQ)hC zoS?wT9@mYJy)Q}nt$oB;>1e?;!<(j3w?kgflCJ5NZ;!rlVv|hMF25s17k{7No%uxd ztz0&fm0a+;Z8r@v+?`dwc392TslIhbMw20WF-CwZddpA-m|&LSL5i(Q=3*jT>9ff^t1mA;ydDnCtu#F zyLsQXi^b>H?BD5Ld-RRX@!Ro5%eS8TeIvnQUeFVH2b*c)FD9k$egB_9`RzBEH{VOn z$6ne}`7HX%y6?q%?-o>Uo?2mjPd{5SXpVQScI?z!(R;0`{LlaOKK69YInM*%-6?@aioeq`%E zgQw?r*}r)ApTXes`tP}~{lDw~&1e1{fHntS-(y+)uK$EZb(fr{%( zUMnto?00(i%}pOIcy2tIEP17Rx!CcSkyBn8^VznA&wjlpTRiDf)o!nTj%QUVZMRBu zw=6MvHL>9G*R|%MQx-QC-YJZ(Ik{wSq^s7C)xq4)&tK6_wAFP`h3Rfb)J@r_vYS?-F9iI(41VYW53$(cu!Z)dL0wAQ%>~OtEhz4h&KQpsgowzQU+KhJ6!e?8NYZTW7B zY|`xe&L{X6;5j};Z{LJd7u(+Q_}z0}az#A-cW}y9dAEyf54T>o_`Lkeex;5lEq3)& zYvc{qUC#e~-|X)3x7qGbs_n%7GXxy@dEaZ==2Jf_%C6?Gu6<%-6Lw$9`?$)S`YCBV z>C5IfFfcX+N=e_qV>tr@gV#E~mn-%(K0Pk~Rd!0*(@Uw*QZw(&l3x3L?b#!S`+jSm zQPh+RdTlJRc-lqDiA&Y?Epu~8(Vw$*xqImKjRk6}^U9|SeK=-Ua(%VL7FXHg=hk=a zUh*3h=b6kg-dOUw>&)cEeXg&PAD`QQbZ^<6ZMh|jGT)jXdwT8f@<-|7r3*uPyU>D@J9LNPd#m~9$v0;`K_(q`&=%_rq{jalCQ3N-=%;f884iD6?qqO z>c(Cxt(miC<=L~lH%%^@o}GWgdt;eycjEhYpI^BPUr4d(*{-RGKDar{U~{IvnLyvO zKStB8Yuav_Tr{~TcPf*jb1qAZYu^;X5|zmsFE3yD8JJK!yEOO8^v9ju93R~7h}6hv zJb3=d#7x_H-N8)0ZI5ESdzMUCY%)Jcwd9i|S9j%3v8f{Sxj#(nxfgM9^OAKjn$pq57K*awKMq>HqL95WP~)t7D94#I zXU^R84>tZZbN*pt*?+%I#LMf)Y~9Q5mbYH&Z2ZT-uh$a|@^lnJLXurGvop;Jm%669!mD`Bk7N!f%~Qd1mRw1eIBKqGHpz3_i~Qt={tpj! z6_f{=PJD9XukoI)o2DO^=SV+$bhs)^IaE>b$>D1uWxB$1XWg1E<-JYMWV6p}y-U5` zi8GemJ6)8nYs>B*e7dG;>6JORZy(sLd-R9+al4!08{OtfRr!8BCL8-)^UM*@xdI>@Gdax}qq! zI4R|;vDNjrv6KC>6QA9xGWsq2VGm!>-F{Im0kA5;IW z&5JAczkRl>;<0?Y?%vMXmM{7*SpNuH`+3^VtXa=;SKaDayj@%>x?*nl?5aN*cg+r6 z4p>#ZQ?x5La{aRBk=yoOz42?()p_~TlWv$=-Eo@hq*K(r!XzLjI>Th`k!2xIW-jUJ zIeo%O?)c{7z;oOC#XsM^p3M4m&Xn-O=}V)IEp9$JXXVT__qwaRl|*Ia=f1;%64x)_U5+EVslT~Z^6vM2zpWl~Jw2AVD$ys{qV4Xr>Beg{PGRnv+h@lOwCDId8}Z1Yx%~_8t)%wUcPOtRQKbv zROUV17coD#3GDo_T{*jO>b{hdmU8_T*Js|orZRQS$)}31Yy7rqIr025%ec=)H1e`agaGK2lBGc}|4C%0|687g@vbH9|| zy8h@q@s|;II-iDhUJm1Yd+FJ3wuL=Y_pMjWdld5bo9pxqmmbfwx&8jJsL+L5CoXvu zx-2$QGS+aOUh?AAt!H}k?HAtJwPWw8&&#$|B+Y!aKWgW-_kO#A*B4JSJ99_Z?(wE? zIVW~|TYM_LaV0-y+pXInvpn~<{i<1Wui`(0pvC4L<+Hb5UAgA+tXt=<%O1a$v2Nx3 zN-6D-8)_P{ryftSD33JRA$n46mnLUL=O)v#pz6RCsnM5BUe64P5nH=$+S8Dg5*Y?FlB%bDJvQsfV^-_lyE7v+`D@?ai+*=%x~_|_+3oDW?R=YdDd!$-=3V5HVK5`hn!{OlA?G#T z?8sB6PS1I&_TJ0)RP5Fd`b^Q2?o2znQSM90{$*C;`bGcLbmvq=UtSXxusKu9)wQN% zpI33|lqY{)o(q~Yd&=yZ+XV}^G&vV0FXa5JGRxiR>)wvzbDpb*-SwO%QL*jOLhgdV z_@i!z=Y4mQakCWPKQGN%*J`hB+|;#ORJDR??_AQ}l;#ray|Ps3yyrTum8mx@1~bRJ6&GGrw)!?i78S&8zHk>_C@Sz*aeZnOl!$e@(gh z^WnEk+tE`?|#7|k8tsk)a&eiMfQg?Q*$p|l>nI!gjQ|FdjUdx*PDeRee!t(l= zx4XBz*Ijz{T11;~?s}7}f_|EzqEX*PHcku=)!ND{=$`v#?~8M>z9zGm6)j)M^6FOZ zx(VTypIPsm^?R~V-_Y|I*H$miORMizr$1eqG4ZweVt)6McTrDrtRu}Po!prv{%Psq z!`d^&m6ko$db@PW)fsEMCOmo=G^OLI+%}f5lgkv3=W;43d7AV$zMeR<<6n55@3o-Z z*`4Xk+;UuZXNNszy?Ll}<^ez7*n<0$fAHN}vSD&;=eaG--MObS&S{GNwA;30&SCSO z8!pcJQ)ZU1WNvQQvUsn{i4LVe#XQwWbFI&EZq2KsP88RDo-!p*?>O7H{W`UqKCa5k zX|7e+rMc*9@$#9sTnyi?OWB-Oz9DbPl*N-dwsTzD_&Gm#=iFF}^rLHY%r|tgVfLxRr7N!p7R|c7I_KCkm1A*ze@m`BtoZX|Vye-miDEg&;_?^GeybMy z?5|%{TDRq`Jo9z?SgxlA7nQ#G9K876Y@g5d7xzyr{P}+6j~zQN^xm~*bB0h zs72TD=(45aRZq8DZ1r?9>b~#3C{DjE?6Om;zk8m`bAhRQUawS@3R$FVvZVivr_|}` zbIbl7TXbvtjq0tZ=d4`*GGfBi+lBK|CKXK%UHn05b=@wOwzJC@9xvRi*EZFtZ(iQ~ zD_s_Mcd|#ET$ZxEz+nC1-Mh2yEck5^xSPK*UbRT~_L1MZ`Lkl|E2k8`R&H-~*f>{X zX_V)6bJ2Y_5;8Xx#>z)eW#>AywsrlMtoE4mp(|51>-N4px3fCpVrsHhOk~96fc4qU zq0Mz$7C#RBaOI=*<@lvi`M$pvXNO+UEw>We-K#T|WhF<-p&CiUwy*Bd;j=;>PJK2d zq{1h(`cZFrZ1<;Z$D9|1r;LAR2S2H}y6NJD*>kg38Ah`O(SZ)#!Trnm9i_PIWZ zo{}=XYsVkY;Ofb5Z+7(wysX&M`gC2>6)|6tE}QL1A=>IaOJ@Dm7dSrYU+4@M&q`12 z={f5fKUfo%dC~uX0wI|9vdY#S7>g98lum9XH zy5o6L=5^7T>V7iaXCsbJ|CArQU2Xf86|?e|{jfQF|LPr|N9*3*U){*%nAKaJo!@ua z?r}l(nxb-nvugZKpF5Q_Hmoh$Enz5ncd55`-aoh2)tj1c)p{FQ<;^WO>YBE8+q5V5 zUaSwjD92D_uY3P_Uh(ChxqexZHw#;yO`fut?Z>)1%d>m8TE5NNa_iD+tDC-erl&uZ zl)HWFdRgSM`gwP6q)N*EeSL4T-L-qSyq=z!uNynX%)9bB|E3mIUXvyjMNOxmNmtLt zhn+lma&~dvlO=bS-nxCS`t{|yc^R|B+I#b^+3v2Gw$@E7SM^!+c2SR2w(qxY54#(k zyY1Bc3k$zl?|1FL@$&NKJBye52S0V~?kimzTeD>Sd!INyzIQSrODzI7_&3OFyeob8 zamS<9qg&cd&)r|Y=j4W}NF&~Wk9+6#Ufo@sv8wFKr7o44{lcp!zs&vKnOn5_@hstc z7W?jOy)2ispV#q4q*2kvD!s7RH!W6e*%Q>2`{U8>Xq&fR-oMy3b@It?zcw$7=v^D_ zw>$4za?|qa-m0v-J5T4`jNGr`YAW_a?{w#fLnf!f`=`xpocE#i?YcY4#VU`#m3OBW zJTtyq<(YJI?%ulnr?W-xnQdLQ^Lc*u8+k8Y?zdOBEe=oR^4i?DIJ4l2Wm);9wSOu# zkAAzjPf}l1D*w*nkXo*tpOq*7y4=5X=ZBY7XS-)yPkQZV;d*bgZr_}&fT=sS&02a> zD|FeGb#*qCYi1is<$jY05r$YpQwadV0D^&-B7=*ZlK$ zbUxw#+43+c;j6N9o9d3+JzK1{ihAv@jW>*Z-;E2`uHXB?p7FS8>Bj>%X2!kysCz4-EYQelL!W(M!VfkSE1dw1MKR1?L zt@bBW*-~Xs>b_^4OJZm5ESI~o&hzSa(XjLU#U=JHX4wB}RGwd(@@LN8;>RD^y1$#w zpX&DH9(SE&h)m_7?46F=3SQ{E=k4}8vPNv#wUtXEvU%a?`M z{g8QirugM`W$S_`Z;iezo>+DH=G@8Ro|UIlHhz3ECpY-)&f>1O>7uE}1b+OjnCiK0 zcJjlQ3)amsU-FB4&eSuR%gTaIt8MXZU#@tnXH&tHB@0(Zil@3v&h-snam8!ip{mOF zGn4zXqQXqJU4FjSKI+z|_tU#3zMZ5e*BQ%m;@ajnrR-5#BR-lo=LN32x+${AH)!H* zY1utq;h`3}CtsJl2OL$+srqI$E6TC#N?qxWdY$!$4H=)rO>@0o6uW%=FT0uP9tXC! zrr$c6TPJ?&-0?cOm#^wf3hjNeh4WO?cg)T8H`kvsExP0M&d}EST{^GsesU68q$w%# zvhQnr=8^3erYw~#l}6ZTt-DzfzH+8MNsPdxpSy6VylR5XET)X+CuiENZX<6Wu^W6=X zA}l@{_hj!{>3{T4VMoDqtz^+NVL_@}J#wd(=SSEoIqll@Pe{sthJ%;W zqF?#dzg%+B+d|aZ&v)ILv)Z?!Zgp1sPrT+`x3DwU%&m2A)9p$8A4AUeikjJtbQjx@6O~quV%-sn&khmY}Uq?otOXe&#aPO{c5_7W7e$@#py|p zJiUyz7a8taeOvOz)Dv5GZJBmmBxn8OmEBuwAFkiI?qrL-eSFHUe$&;a-&K~$x?MbK znC;2CDpuy@wn?L&(>8Pi;USgS!~tggQ~Cmv~T2m`h3wkX6M4v z$F6%hcSP^|q;tCNs!sK$o>=qc7KiJOytu0wx_EZh+lAcvMi-`ve?8*<$!(pfyYqbu z?>?^Vt?I95U#?L3`>xmW*TY-?8B%%H@KC*~|!@%!?dN3pKg(`Du#{dW7^TGh~pbEanf(K5`MXLh#zi}p)7J-@b}r}gf{ zEnMN3C0vqsPj}@Gi{smjc9#hk-?@{Ve{eh3)GMK(ISV@`%y3jyaa)|L6}2}wsHf-5 z8RzgK{n+UuPtN-pl;oQGZ(8#>s>w5dQSMLgugBhApZc-8GW+i9_3~AGuFdz3%rsK* z)N|~g^4QPrl97+uiyPmJG>f7Z6&at13cX>fdZnar+m+e#I@dnSnAN)HO{Kk&*T!`x z-5)Rd<6Cw6`(n0N)4eUTSJlj(d#dW}?93-OCv7cnwT##^`Q3WwAJ?9{Z<-UhMt-*1 z{N$jCU3t7&%kHPF-B7!@d6rz?`$X0BuCyz%(Q=8ZvHrGhunmv!2g- zck-*}=IfHzlGm?UqPu0&_U8|0iuPG*KHW5R$FwQ0zn%(QGG&pn-CX~3rqW@DE2O5r z@qh8o%WiY_Bl*Sa=6+VG+&`)0T(VccG1u8=tE0-NJ+=zpep_0g`p)^g#gq0QJSY5H zZIe*G$G>}Le}&j(&QkrC9KUGK=AVbPUz?qCzHeN&>W}?@hQFEhlE)h>Uv0ltasTi8 z%^5xRr|*B69{Zo+8-KFJwKIllchqh5Btw@BN*^ zSJpf4zcFv}_x=AFI*k;16mBivRNW_k^0{+8v()Zy{BGv#o`0tHe&4%)hWoS4E9#g3 zdj7oU{-HeCTygo;wiExTJg?u>?YQdC!JkpzcTeAR{(`!DeR@^=y=isZg#rueeU3hn z*Zsn`Xk)5SrqSMRzh}Op$2y;!K2mt7TKn|FZz5Bbx9UqQo_14e(h`*`Zi|eRm4827 zYh|@=Z+D~Zw&#_F`aH?cJ*zf$yV}R>Jonyzm+KjsyFGV`k%z=j?f!H7dhY2|>7~N{ znY*`lyjMvr_}*guxIEeAh|gk?!;h11ecj@c`b_cJ&81t@c-y^vn9GdyT^4TJmzKS@ zROw5`>K9W>-po6nJ@bCom)SQ9Zdy1^{O-HrczUDR{E2crCF`gBXE=V><#J`Q=Fa4l zCp+(zX6_DqtbKjDsPv;biWfJlhv8N}drzWRo zs-E2ZX3cWn!k5KwmY=H3+xL5Qbl-n>8p3OEv@_dQsd;Mb?dg~e%tnE`M#Rz zCw_%ZUlzHu?Ce(6JS~C1z}6iqQ<5c?ObYZ=ndGVJ()D1Wvr2iYn5FV^zwn zip{gUwzX8nzAEX%hw_4l9|{(y|7WP4yhU$j`NSy2o2!e3I;ACU&A97yr{LzYoinxk zjHmBsT>PqS=I&Hew@)6ICKYLFbZ>cd#RRzV%Z+ z-T!mglHcue>Vo2PbJNT=e^Rd(Ry?Y*PQ%SS$NW#mwNLB5i%q^M68P_@wp7;c#1GG{ z5?jh|s#)$b=+0E}S$a5Ia{hY#k1t-?EmgcCJkxj9!ZCA4=Bt^>d3UfxEb`n(zy*s6BmZN;`@vtc>y>#>*K>*yMzmc{b1Tc)?pUivb+bL#9DU(bpaJl2fs#Ehh zYIaqBe{^naI_rtD2|5d{R$P8?wZ7hVH&LzYPp`56sqWU_W>4mS4Ez|9Tw0Rv zS+#R=%G0vyBVS)ldh?$l`1&aqYyH%?6J5R6qeN!KH80Qjo)>$tpL?g*y0i0`r%#l! z**4|Kw1D5*FK>amC+p_rEvYNKDCx!D#WzWB`o}j#TV? zf4^Dx^;$=lt*?%d>FQOJmftgby!BS)vk9x0^|r2jJ*#)GIbYEGv~PR2^n`Dlvd6RP zU1x5_fvUM@?sQ(7=XLgZUdrawghz+ZY?~7D{@%3HVY=%3o}SFPb46)sQAmr*wLev> z!wXLzF7G;jHQd_2;Z?44*XdJbY}s}u72705do*-q|D{h{C++ffo9~2^i5i)0m$t6V zN|OmG6FTj>`G-V=&F-bzf;AS`5+i-zPxd}HXOHfG2Bv6@ezwZ(Gk@!Q;)IB}`Pm*Odx`SiGF`Z@jlmAYrz+1kg?+Jqi0KGrkEVb8-e(@SGk z`i4d?=TZJ-`0-nzcg^11lXAPO%u4LpCs@{9v)PfCTByC#eA4pTy4j0v1-)CVZ+5HX zX5@#Jex)0a#By}(Ir>CVes;8CZ6B3=V$ed zyj$C)67J_cUC+)l@5X-yrO;mU4e-7VJ^@r<(be zxajI2kF%ZYlZsx*lx}|gMy&MuB*E*yrp~Mpod4z0o%FM1Pjx?REnKh9&%aSE^ZRnU z)Iag=XI_h?T-`I*nd{E=Hmy&)CVyCG9Z(n2^Xm75)s_Z-OgFzeCYG75c5_$Jq#}=9 zlQ%}4(|&*H*4{}~;>!Xzt=lX5)PMGaDJRbx7d$HlgZeGC z%un8f!v2={f%k`ZZwjrsaVO-Is;1HH3l@9V_?;B;_3kvzKGXBdvv#SFY_IDrCC`O^ z=RCF7sCQ+)T(5b#ea2Ugx3}-Ot`EFh&+B|NvZmW+*Oy0MIv!8G*ZHaR>47B+f2a)x{K8pIOYDVT{bsFb-n&`yQHW%?Ssds8ohJRj+f-U`aS5@ z^v2LPCF{0V8J0x7w}1WN+1$8G^Ys(%cWuw}TCJ{RGO61?d29OFsi)q|F5K?vdgtNg z%fiQQ9M6tUndGi{N%ds;@+sF+KP{Q3HgH=gvZIvxAN?23M53Qk|LE%gsy)92Vfl@@oV^;g}_ev%(_<@&@!MgE6o?$^6n z9K3byXVrYy*T=qcUh>ROSe>(4+HJc{;bZ5Zi!hHqe@3ZgS5ic%lk$L;*(M|KD-qm_rL{A(!-}oir3*tmW3LH$ADObEKYdp|bN@1aF8j-Iigk4s-`6YEyD#~( z&i;dZ{Ud&F``x>`zvia@JR0AeSI?fCIN|u|t;=qFxO9DHjGL-!=i~=B?~5Diw3%d{ zS!HwM7ehrIbp^I>oB&Xf|2Vv!`jX-(;m)GKhxnx}OpC$>Blk2raI_i?sI-;VCMYP;gd^ZxhxJ05QT zv3P~)y$Mg)Cf@!j^UpiU{ClX>{;Y)AY?oF`Ej(Zr>>qKyvGabk_qvYVt22Bjzgwz1 zYwC?xX^SMz2bkLM%;ny3WPjS5yH{?tJXV|3^X;PfZKt9ywom+WFIH{%YL{^7tBg!W zf}g=WH`CXGQ%~MMz9eLwla_y@?}z2FQ{O1qH_o-u))(kuOW8KzNl!J?EwQiA0wV}o zV61w5lH>JXuQU5N`m8eZjGyf;n)V}V$Mxy$?KdWyS6`lQ^iO~Bz1(i!)s<3hqC3~~ zggz}*e;8A?;Ll29t9w7xuEpr{#x{n_9#!mFo|bt^n?-%S`@_w1!&5XeYsKa#1+R#R zpBWb-J{eSEh|BJa*>gQ-;b+f3&S%!U+E}EOuJ`Skb^}&q+%j3xaVer(c8YkauF)-y z?gxSj9a&$VJT+I}yZJs(bl~RRyep#05~?5i7u3vsc6i?R@So24=N}~P|5oUs+BfN6 z=&9d#Yi`-R`_EvQe}OOl&&T2qnbUXQ*ZsHHBJ+>RTmBpS42;&T_~*I#-P1Qp3}0CP zF3O6piu(1WH>Wh!MyEYJyNg?G!r#(ol^wz76OT-~@I_mvX_v^dX_Jjq6_3dUO4bM& zGCzp!ee>FE`8J{5EPwhujj#R7J+ps#(Yoa;l1?61J2rn}5pPabz-CL=^-`I}o45<3 zPagj&cX?miR#T(NG3jC-WgJ&byEE%f+PP$I$ECR|rnE;Y>veg4{h|6?x_P2HxA3HU z3+^9Ef419wvrKQixLkE&vD%gRNx@xpC$Gsi9QBDwV^7&7y)t-}-CS$8#h>JlNVTpw z->(;^+x}8|(#FNTr>_OwSGoPw>rBAR1coxtkL`-}dXK~9igv0jIeGF+-qI5(s<|td zF8#fRt?A^YXm6$HEOnLnA@is7pGeOCl6_v;T-U~Tz1&~mAgTv z*vh+)URPcVl?~NRjcr;SEqCI5^ts)PKi8K2Afjx$!4 z7AtyZ{R!7URM_itY^U7K2Da07--S|I((bHT@7)%tnsjSk^4S{J6V17EH;Nwqk!zmx zQT%LY_mZG&Wh;59hnc_5m3)hQ*8a0Ua=WhlluuGW-xL);n%@<0B)4jv;McC@_jL|; zzGdgu6FU`Zr&hS*)#4LxmhZ_D-Dcn9HRn)R>TMOfzb2^~9VdEj>Fuo&_kJF#dn_up z^H0&7eNw%(Ew1uU|4y$uZ=bc}J)_@~szdoK3rsA9U-)v?h}&yWFNeRTa-vee(Lksg&oPJBpq@+nvfk%m0yFYL#Y^ zv;NwuoYZ}0o1{MNHfrCfmgy_HWV4jDhhAsyvV@KxnE<#tvPm&^>+1lc^Z3hoS8J^y0@wJ znI`4HpB%?rPnpl{OFua4-ls(Q={52nJHjuS`?jsV(3f*;r=P@&ndaMevKi7D@TImbzeN zeZ@EX4UYOdpYFcfY%`nKw>`Rj#Nb-_i( zTE_o2v@MP8=e_BE;N~x#%jUER;xu5*~ z{GW=y%KkIK-=;3COa9OBd-XG|zaPr)OC74avbOe{|DDJ3ueLwcj`!$4dq++F)Azre zSM06s|B)9sEYZpD=0N0rlj4wm(f)n)XX>x}?f)(C`l8TR=`WMMPqQ}^zNqh1e`DV4 zclveW6C4d2W;Zb~uvOZK+gR`0|MN+m@7rCemdi?Oe|hsR-qd+b?97eQ+VzW_`89x0s{;YKE^mOsf61M9%p6V`| zWTJi9|B~__=7rwNWENNI6fpTo#1nr{99_${bhXK5g^dxtMm}-5+#TyxkIiZB(F)Ezn0RExvRgKn ze7XCsEY6F0R$C>dCO+f({yen>d*medi&Wd_&7Hb@&R+JHa_%8Nm%3{i#cXcX@K^KN zy5sg|!w1)o$1Z+UZK1o;>Z65bMt{$h8^YTp)!eO0&jn9BGc~`wFjvpDb;)mO@jElE za$A1p+3Mx|+%zwGz0IpDkJo8jS!`@uBxBJ=YyXN$snsI06 zvgp9=+~w6fj=C8J_U=%bk|;7MuqRZ=Q)SYk8*yhN{kauy-g?w@a*2Ix<-8=3&o>HH zw%Br=R#?}nxpc{yPLIO&DQ@vBJ;4bmHHy*Kvpc7Mc^7)+D^H}wlN*;m={c_L^%6_h z*!tL4tt{%ErN>#-!#lNJ-8wF{dPC9Q(??xgl}kE0KCKNe*!e6VFswu6;R*BpU23~y z_a|S;)7cRcFKeN>ZTHIC@q#}WZCHQmkC?gi$>JCp(?^fi&pzzCCv>Uiw%tA9$M$^C zYr7QDng7sSwB^rg!!;H)hdp0Ef3xlKNypN!ZMr&j?z11wF`2H<9r6B=@YmktOY6mM zZSJ0WMY}BQ#j$rAMfSR?^e?_^-gx{nd#n7*^#c3t%1>%%Xvdm$;d zag$EWUR9CBlE?gSAKNXQU+Oda;``+>uN$xal@GY~r8ZpHcVp3)r)A%Ce}`R;_#PrW z_jF;`+T$XVtIKtt%?so>rIQpse@FKEb1K<7e|LYH_x91Ujcb?hZ>(nfTE}T$z1XjA zN7??H&f3ztm-{cu)R&jPo@8VE<<@_OJ84_L?_+;p@odFCZ4Uptj?b)qtKRw^xW0Sq zhpO6)()W|suTaWcZ2nJq&u(q`8J#cM7uqEMntD0i-@57B{r?QvClpp3;62R1cchOY z(tF!OcHuN%^FGJI{|sS=P3^DmEnIcSZQVBGRi@RdlQ%9>bXojqrJDN(x0|1~JT0E~ zK~?wPmo0lUR6p{cJ0HJsZBps}**Q0tm;X~dH|^}n^GZ){eU+KDJngaby)@_U=yQ5G z*AF{xm0cI|?x#og`i)L+wz0mQGG&{Ki$}(i)A6aQPrRxGD><_FTOBfZX0uNA+?V_7 z)y}8?75l9B^h>x?`P@`qb1AzeImfggsy@A!cKhD5y4Q22{^Ki{f75irnP2YTzU5z9 z_}S-L{Aa1ZqEF|alZ=ntRwVax_Sf)-pxgIed;d6R!+kz<-{oWf8Q#~pk*$0#5t*MU$8-1r-lBt(F@U8V|)|E-dN!d5% z>|&SA`SqK1Zt0Vj(tk_S;#pGenrxT+9J|xIMmHw=^x?yAJSXJi>*V(y{mJ)U>ejW% z-qZ9lo_%>eZ7at{J?HgIZd-~z8tr~1;`{5#%%oRaeXi{eIC=hL=asMdHwA9gysxOf zyViT>_oboL_vE;~g?Fd@J>LCi?LXb9BdRCud+LvH85LC6e%~jO`FzKBc^mJxJs~%J ze{-nJnS9`;il^`QR<58)N?yU4N0gM5J_!m6|K;dN-uLxM_@%UR6tWDIBT4a@Uz(pYP}%?``W= zeB6Fer=i_(R<^popTN^f1VJtYhz+=O2qEuUibS#J07ib(+kNH z%`Po_Z{X`)oZWXPiK2D5UgS>cYqNWAc$!@Ma(Pwuv6-*Z>iFcAH&^>!(LMBy&2{Ci-FaRc=kA_Z zFh^x~vc`6?-NEL156pYry6$hOIJWIcUZC^Roh)rhSFO!e%k1pen&tfaIwNq+Mw?l| zE0!y!-IEt7 z$`kfTJu25#o;vORl^xr>E46sa9_Cot-LsxEMP76v?p+rncT=}e(>&ta(7>Z-$W*s+rlvkd*Wx=%#Pi9)} zxAXO_tNW5B^m*NS*;87NuROLiZJOYra;Qo)=<3bzlE>#XdFBMK`?6VC>h_5X>vn(i zeo?Nm{Lf4+H~%A7YJ7JoUzj)PKf?^R-nGx7FaFxvnD4!}$W!Nz-=~U|yHwUk%-&gb z#{Aloy!&3Oy9BzXoGo4RT4!a>8qIat&qHosIX68rJ2YMJhgj&e0Qn-FSHC%oW@m+H zURY|f>~7|frN{1oujO)5nWLVSclXHSBHd-P7W?L8F5hbMeeuPdm^CqPvMYCl>)t!O zH_v!`kx+2ULzAVFhXV7AdKO(-tg&rY=CaFnf}cX8N!Q-f5b53$!)BGZelxpr~-e z#&v0FF|BL71ExUE@(-S}+|qN!F~hFBpj?&7xfkwjKXkZ3^pD16yIZLrlTP2C?W9_} z?!;!L#Lq_`sBZCCb?vLrEpvetCf<=dEw5c!G&5!E>Mh$MyL{hVSNXKXve@tOzR6eh zRo>Z8jGcD*gZtKRr}lORAFB7t+yCV2=DD`-mz*kiqN^9Yy`6XUzNwiurze(tes@v3 zFtfZYBvj;S>FJpHGV=v@&RKVRQbqC7bt+;VmuEWt+i5A9vs6v);>|5xF_DIswuQva zY)=ptd6RJ>J}m9l!jmb}rIWaiwts$Z;`U_T++O{!*+HN8_nY2)+!MbZv?oCMMehag z8)dnl=WWlevpTnB?uwJS$Bs?Ar@MRI{a(4V8@F81n)A*zao;|csuMjTZ1Ta*2dwy9ZpT@kf^Xm{~LDE^zr#k)P;vVSd0IokcI z^i0*2+3QX`+UB-7L^(bzebddFEvt@)S4NISJ|(rhkeghL~{AR2)e2I=)=$O z59==TJ+BLIH_5nkyj5&r*5=LXpF(c4{d3Ds{$M9rlgqRH@Jz8oDw!8jjP*p0Nlg_B zDP4L^#l3dRuG43BOg34&JvvazXHT1(@rsjPT7jkAp*~k;U(0)ZcWU>?g1wv;r*s3C z&6UdDxP0S$k0-ATyp)}*K|{c}G0*A>oz#yX{RMeV<)VJC_*olv?1;$A6_ciT%x!og zR(c{({`9ZVrOrG{x_^~({9`z0RXSl!ahgr)t|xiU*IQHre92 zxjS2{`IY90%B&81{^VinP0t^T&Q7t(-8y&KnGbjF_gr$f469Jz(Ys(8_j>=V0-sxL z_q8W1_RiDjVw0K8DVL;d=FxLfElpjo=-9k)y_7@uxAlbl_1JR%m*%{{Uq-JhZiz>i z*z|1o7Jq+UZvV0Ud1Y_)&VG*({%CvVV(8?zCYQR;EW1!Is*)T!YjMV3=BTW&N7tT< zzL7h4@CE||TcC~35AMvfvA2%=S;cbW7JtOq^*e4(XK9y6SDG^QSE%2PRnu=O#kRRD zt2!6#rFv+My3AX}=Sq`ju2(7A^0PZAc;Bo`Z??R%pHzKy%O9g3ukw1!&81#lUuqv; zSfzc^=;)<9rh-iC_DfByvf)8_H;>I zv)Zk-F|#W!MjYC*ZA06()q>N~SmU<6$WU_Yn6hfS*ur-4OFHqHEHfrZn3%bJx{-Od zM}us9t@^ zZ(e52%mv9w4{74me}+}pZti+0_(fH!N^+unVC~bM*6uSA;rF*j znqB_n<)!gPG z`|LYo*Jnf&g?@8g_{~XUq3M&NL+`dr&n*m$x;b;A(3>^cw-S2(wB%LRG-s5~7Cw2Y zC@1=MQCPaa^uv<4)i(Su`&RcZDiwCQS}e~eJ2zPNPQ7yO-L5(NCyU)*QMxBAp<>Ib zA4Q_fTb3{0@@VS4^*5y^xurkV8T>ehUtcB;_r_=?SMR_r-`;ZO6U%Aes%4=uy=kAx(Bs%vbY zC^M(pWG{QjtM4=WKlq(m{5VcYs!L|7bneqH(#E3R%W{P?COl)TuNBmr-IDcEEtE^r zQF&4Cb)k6eTax8|`m1ejCh3=+i0S{u=lv}6sp6dt58hFQq-W7WFtu%TnV~*6L$sY&L5;KiORh zytH3>re5HrU0=P~Tw)h{%-(rfu2N?EWQp7f2$mdp;%brU+Z=6|Mm$Kt`!Q3G42a~hDvTVKN@}EKS>FLLz z9e*s>KS-Y&|4hH=))T+DO}E~suD+XDwpFH5?RuxBtG(8T>5EZEwfkVB+AK(;+9@BS ze_AZ_?@JZ9?Y-&a?pM`6*6OC+ika-6D0j5&%9cq}TGrk;;+Eo;;-;W29%uU_zVD_} zmiLuX+xn|tURUj#I(5mtJhP&W?;rM4z=QUF{Mz0ady~jD}xR1?b_7nLZ-n2=DELBr|XPB!u?Vh2gN3NHej-tgR zV=tdyl^t1ftCK2jwcZx5>*UHKV$pMneX~#f97u#)8_Y_s`~8lv0mA&d;YZFi;7Ndlg!mBygs+kWy(^Ote~Eb z?iEkf)fS7~nSGI~y`8etcJ+(>x|8`gx#f(B z>v#6G1$&|bcl`>xlV9{>Z?5;VCrf9ldZb_QU)@&A)4j6y=z)#zeWWeUr)eyx*C*GD{ADh2 z_u0Gn%WL0D?^nehKe91%J@fV~f$Zy@$GZh}Nf=o_U$gwrbc?6o&R_l8{mo&5)y$u4 z^8WFL#=d`9|1)Iol%MtVho9lfj_by^6&P;4zW3tQ&#kjwPu$&?%)dx3aav;4zux1K z(Y_WDrm;ugzWz8ja8jz-@1J|juB5nm#H6b9ug~ZzdZ{L|uzmUpPdn-9iX8tL6fLbP zYInsge9Hc_`?G7$x|FBqd^cT6b;PTurw}$M=QY z2-Gca=PMP7T&q>5+L|jnu{iG5Nq{yC}A*Z|yoS{+&DPj?Or}Y1_iGrze*GXSg4gf91o3 z*9V(T^7%gUU&{3`Stggvo$#*lxAdCk1DA%@mYl zzr1ID_2)FJx@(Vi^2Z5fKU~tg^UI}+aT{NJTpQC`{YHDnOv8}16-H~FN$E@HOR`;}4dQSpUIayLAb^yY?K z6r3qIv-A9%jmsyWt~6+ewK_~~=6^}Ow(*PIwilI>dDHac=h@cx)?Imf`|GW@lq zWvn^*ZC0l1v)~N-u1SWg&IWF&Yg}upU6@_m^^HH_!5bsyhsiBx&eu3t91j)Jz7*`g zL3p-LaO{>vIyP+cE*lkvvT$7Ye_kuute#}Jbn4lNfUf+u_tGCV zT+i+2epEK==gZ)=w=UGm@0_po=CNelf$v+gy-r1j=RW?h5yS4L=lzztLTXl8Eb}v7$&^=Ra+u5T#{<+0^?3$@CWnG&~ikbGz z^=`+Sr&?O6XCA*geb(N|NAub;w{MMGmtk=&G?mv$Jig0Jc(3{^?VJm8Q`x#^J&~Vl zD5?DQH;bv$@3k*YAEv*^_v~()nWSH}RX*X4@V(dn8Ps{-{xNs|+mxJC_EP^z&HR1! zk6b=Q|2b-(872PA{y)R6eM#%@v)6uHqyEfK(f5ayEW#3;I}{j=Ei@0;T%T=yuRZMX$=Tr@2ZP0TJDVlhSIYAq znlYipcf#$yZ9CSbh&{ii8b}H}Bfj9ak@Exd{d|ecNdD)%@6hhMRk?KM~74x%bd_@0eNniyz&% z93(fL?moTe4vFgp_j{G>etswL^>mZ$>tB6N-re!%r|sQ| zU3W#F{K#8hZM)%xy2qAzPEYsb$asWH9a=B4Xv+F~Q>{;bjxIa7wrhQxmVtkp#xgd! zoR#O3wtp)0nJ;rnaohCN$K_YPe~gMcyJe@^yhFC#yel(RH94a(=C_%S)I542&m31RvuMM_@NC|5Cuda`rn?qye);6p#Fe+&!t&H|&t%$3I{BJD$WFF* zzB6}0(RRa6=j3)~-LFp9I-=(mn%!mGH6_+2b6K_B_8H56S^sC4w*FtB`;XJcPxc8J z<$uuEt#|xz1JO2g{x~iDWQ~gbfjwLQvL8;LUBRYzxuaC{uh^~5OuMh?7d`Ek)LQ*o z+`i)JR(IY0Bel1m%!}D-S|n!kV%OUB8`eykxr0x&Pg}zeVn?`)0@g?zy(_`++~~GFO$4CcmHmS^n$g z_5Y4oeR0|A_E%-^)BZCqU%W5O|E9j|clZ6)CjveRY7gcei!5_qYjD`l?)N*zX_2X1 z;Of5ZR^}F=X^;DKT|7MnXU_bh;JMv(>*hzrtvjrzOZDzpb~~?Ab?df!M<(4ncQVfG z_3^LlvS0Gn&0F;4ZRv{Ad$Jj?CQP#~tGK5e9GzXh-Qv$8mit#kJvX@-vT?<$pOproYZa@No}SS9cFD-I zl5gkVi?fg4yfgW!-04>1%dd;yx#jt6TNV|sscRcpzrl#xYH`o6m{RVJu9v#1^G@}$ zTvVC-#-nZH=hUYY*WTYN9;&@L`^1x%ANtE}WxuSRW$sa_LVlIiE4gc*OfTidyuJDFYb zx3`ElcD>@-o}j>qC3bt~H(own`)ch@TYI@z-&O_3t$vXGEK=w{!}M#ZD~jiBZq#2K zyS1~hdUn2=(U0<^Y&`|%>l+yEUq4i~?9}wb$9oLkCfYLv-za^%*~im8@wj&W=RKRg z&B_Y?5V2|b#w2I=zs7z`{&C!jbK7oRv%FPu+0wh)UwIWxs#}%0Uh{$Y(z-aa{3Yfl zaYyFlMfK(G&Rx6G@0w|MxboB16JA0eymX~fDtc=_=4ICNY~KH&s*&R>^Os5g8Fv4e zXdC>+`fuk;`|AG;KPD@29O7L??KwS*`Pzsigc{ z`O?2H(_a@FO?8= z{kE^4Cko!*RX$p4wdZ<)TzW!|hPovr=uwOLPh?YJP^@?e#XaDy&hdUd!kHbfu&Bj(d~u zowvvAFuLFWAwF&Yf*(1x`G)>q%KtOy#{XFeDQ4s!#6|sUez^Jl+QTojQq@ue!dyZt6(|DLL+{W=vBjhOe8+^^gx z*?(Zh%B*YKHt)Tfb@Irc?P}-yl^IWT9hd1U>)ZFfUQJnWx!|s)+kWSLi_ux{`CK>M zGc#hR-b?$eyUF|3pDm59j#~Wj%Jp*#e;JjWUs1TTJmlc!ABPH;*NPsjwUd+BS|wv# zqp2rx_MEg(LE)zCW8D8l5@C^|~!=63SToKt*SlGi2`c^CVqi&Tf*?af_w zcHQ#H|0dS;?b_~aYAtE7TW4O#`JEOwcB*x(naM=mPWW9&r|yQ zpj_+_coiYFzJxcyu16{`)}_#)6RY3 zVaUQPy^q;ZkKe7zn3j_gd9Co#V(qQHr^-&56i%PIYT18=uHNJS8T9|Hd}Xz4;)*ZF zqxNfW+wzb5M~v@wZu6L_uQj)Cx}&FYPVh~6y6+V4xJCiFNwaN!NLN%@z3H&IcE=<; zx6R90idklBm+MQu)qA>=x27DOa6e;KLT6#dlseay{ytt+YSO1>S!JKisbGLoQGmhLBQ&Zg97amhz zE;PH);Ow3Ab9Q!F`6(YhKGj&yJ^Lp|MeXIg@@&^1tlLm!{3Yspz|JO_<7*;|pRUn9 z>BMIm8OhZf{lPly)$;PH$q%=?Ewz(1FOEUgRr;pIR^s0&YGk<)y1c!c-M0K_pVZ(tQxsrzWGGgn!uSFxCcWaGQz^WyKhPyUXx(ia_*R| z=DV}JYg+%gyXts9Q$xm92F z=314$zJB)dg44WZi`QEF2yKSx{?_<2lQ zvZqt&E1lhODm#sE2VDy z@i}Vwd}^WP^U0=he&@0*Zp~x;ldiU7hwSa5mORx>o3^VhmtB3X>sFb`!<%vYp0F)h zbb7^{=IO6(x3?JDS*dLCJGATQr&E)CR=w8fo2bcECbM1HyYHdUt%7NnPE469n0w94 z&1`Wy&um?Pv9DXMHy-oeS}7~_Rr2StYhP;fC;gnR?sDa^O>Vu<>8U$c{k--*a;~ z_xzf-b!OR_odLHxTcVdPJvgIf+t#(y?^wLNJ}>8queayvd2aKw%D?P9xnqaArqh0D zlU3i!yo+y!%{zQ^z59G`Ket!vdXHb+c{;ay$>(RbD-OLWQs*`+niCZJ&GzJu+j-Sb z6So%e?J=CY^;lAwtmxg|!x3+5P1pQsa=E-VsY|wGXE=Yv(xWCTc*6`%J=ost+Sa)hOaH#J7kxH0(Ol|i z&aJ$EB~h*Eg}p{uWD3ZqJn67x0Yp<@VCZ$EWV8uCB@NKRad7_8mum zp6foh+OuHQvTy3#X;q#j`Oo3q|be$e~+L+zEAcR{CG z)UCVTOiAZhs_Ay1@U`2sJyY-e`t6>5Y|+lCZ?reBZuoNDbA{-J#Dg=7Yb$M2Y9C&I z{NroR%JWM%1+UJsnsh5bC!^YJJ~K~$G}rF9%#_enORvD~Sy!qp*4>?>>$=O;EA!-vW!L^%%@4_X$M~_d z$DZY=anF3IYZZTXug-|z6D*yd%R6(b&E@QE+qyd+1*qSSe_H9<6mUD&tf^mcQs&}l zfya|(->NnCJ^Xa9nnlILIcsk9#`DB1w=6p~F|=s%SD{5Wrky|k-L`nYaq0FI(|1mv zk^lJCmE!jr&U^PtTineX&_Gua|z5`zZRP!b*J(re)nAKMY3iWlUrw>t=aTBRkc{{ z%~u@_>+3-`C051@|E6BpMfN=o0= z4A@WC9J?FGmRmQm{O7WV$JgE!?FmUf`)A#v>EG9}-#kCBoO@??BvB2kp<74B+ zTwZ>Do-uc+kcdQ}R7_{En%BgxZAUUT&dBvHJi1cDNhmGN#Vz?+%*$e0GywK`S2y%84| zZ};4o@z{`GXYE9p3HJ-mtemUns_f-?Lg+@Mxa)-5cG1^#vv--!b>c0b)D;}BT$nTK z>$mm7{#*XM{&K0OX6nPgGnvR>q1&*e+k*K?v&w9)#IGz>UkDM zdl&3I`ZhH%<;t`lZ|}%GzMT0!J1r#Y@Ru29&h)+v{l;^Ay8I<2kK8L|7h<9AcpKldurxo&*EBRVWs4(>f&^l);| zj&!z1FI67B_~vQyZJDp1#gDvV?#bH=9wtiFs~-C}J+3eKo7ssqn+|^PIDy#-^|ZfaO&iosW#WSiwZYKy_x3n=HMkSqoQut*-z7D zHr-Quzvs8}v!aQa>21Cpr9W9Jgt8Z;TD;vO@okHxY)Phe#YL$@EK4SLc&}HpS#~$! zPSCBuF5Yst6BkOYcjb9Mnj&{HaH+=DP}O*@>atx{tKJy%O)k}Ud-Yr?;q$p}xmVXh zq;{sBGoEMr!t>D6nLCzlGYai4?4G-wdyb~BjJ~wlm%Xg}xBPq7(Yp8RjvE%|{LQp; zmtH-;tU2ZQ_OC0PrrnfU^WwUarKBcX?E9b~pN`Y3-L9xE&D!y+r~XOaG5_Ntf1bWN zw)(2}ubC&m?flQM{MLE3m_=26^Q+ECmjv$CuzQyK_9pi#;XB`ReRHgZ-)e7tu23r4 z8nWNv%-68z*C!>MFq`6cJ>`a4eZTJ1wNqzR{jyyvyFNsFc}8bSowMS$`>$@bsi&Ut zzV_GCDf>p|{JAV&*9AP&+FC3;qvXup^C@qhB;UF7N2v8g?#IP0eHlM;J~6jNE`7pz zW18=rm)GXc*XF$=5_0KnMp|;=$&-c?XPjK!^Q6XEtud}ybK*0Rvb({2dnNAn#Xih7 zH!12{zTlbf>f)Wt&F<(OHQP~sDOcajGk>k{;;a?l!x!wE_^0#gy{Mzxb^laeK$RUGCjd#*@n5`h`3wyYo~|L|tmTw&d}QsJgPnW_1dBd}1$mjF-rxfS{~!l^u(w zC@fXibknr-(tP*vq3=P?J<1o$bf3Gj-o3i@d3Z{+%mbSvCvJT4+HpzZ=!b}FfxosU z&8b|?>F-&&Rd?$7S-+y9dloJi-6Ye0TIlIs!!@&P-F^q{^xb3fZG->zwzb=LckT)e zoTvCJ^y|Iz(L3MW`C|9UX6n-DxpPcS9=$tN$C<0otrf9%J!m9uePrqLNB160lysUOp1o(TP>`=z5YPFv zpX^a>>rY=>d-A%zq;_!ZzN{sxe^y-1KdD*4&rz$tD{<=Od7EBIhOM>u^lxWT<&R5V z6V|>gOt}?uWA5sAB~43p{Vp#Ga?|+}`$v77>bcXWD#MTQIOS!>&$W8`?cueEJEh*I zRu_HB-Qs`FkW1r5$eu}j!8hK&d3e-&8*ilaB!Sy!J<2Z{yj$=oEX~jR+L!9Q=R!Z< z?sz*j{P@xQsdbk6R=a~TcjfF~cz&t8?u#nXeN(@x>q%%GS=l(V^23d?C%3N`8iuYS{#n|>eBLAMY4QHv6V^5YtEc?F^20)D@}udZqOwtojZZ&xei*o|ICpb%k@|!kru+6? znZY{8=D5hQVyWk=jA|!NzG`#lPGV}$jfJzV-stgsF}1x}Ea6<1yY05&gwOgGzm4jZ zi|p#$YS%`GZPku2Te4i@Y*4t$w&Q23LxexCx<1!sQs%ZT`j-x0+B{`TUeLLh*Zqyx zypp-OB5IoL?;oGTbrNsv*paXIi}z5aaP!oq+(mh(rz|ZzRM4Y7hhO|%+|@$vmgt~w zx|7V6lfFN(Ei8WWlD@cS zv3^!@lZ)PF)@2@&lhf1+|Eaa_30k<=}y`J73mhZ#;3bx}th@P*6Z_ z;JRN~W>cj~CQtgdb}j2iuE%SRzkaN<%kr*ppZGyl%Qrp!*QD0ZE{!`m<=U(H3;ewT}Q_XGHZyE{J+r6B*YhLHw2hrtvhn@>}eLGulGADL@-qWq$ zq{LrzpSM|iVs1#}otxA1x&2QaDO0X`8oJ_6`I@uklkev7I>ptz5rGRfFcaF)+L#@@{R{S$kO zcb#l`d-sEvp8mB=<2z@yBQKfWab1xyGvek9-it9C%fxfHO)3fAuIaWV^V05FA0}&e z{*$#?`1G8wH$T_)?K_U`pYcUJZ`-@{z(mic;LARJ!Ci;;2AIm4$+Xu>kZt0L3G zmfs9scBGuosYsW5O6l6FFUkE`0Y^&K-dwg|^6ikj7I)1xu2-!UUdi({`I7n4yULgK z?_DdEmaI9Wr-rX%%uczt?}e9@hAn)kF1_M| z)^3lAa~m)3Xzg9+JtM{cc>7CM!JXkd`wAzY`KDv8buR0P&%~QAE~_lcT(WeDS<9Ps zGnXf3NQD^W*vUi-+*ZGRTl3_P=+(8|MK>lsTy9r9-}r0&{7*kuhj#B>pEXm{>FkzU zO*iw7+0J-dWAp@5nlj`1y3%X)Qm>46OMTD1q-{(I=_?_v!%kOzUJ9K;QPu)u=c0Z3@yxKSVW|mRNqaI}~wVNKV=62Vv zFe}@8YsaqNl6*VQd|Y#DPVZ5(%a=c{u9)`5b;1&%-hgLX>Ls6ercB+kRn;XW&Al@r zE%x%~oad3hbZ1YT_37wxf90j}9XIynKHVc%_%zpj%2)f7zov=4$?lt)Wxn>*?MeQR z7au(;zHz0>W#5oDG1q>++xRrBeZkq4(aqDc-d=3n6|A*f)kyiw(v#Zpt4yNnGr2j% z)57($vMTNzUGi*)xomIDRz2(#_;6=QZy3_rHlS4V*XIJ=@1yV5M+gZp}~I=5Hly2+Vyj?V2~ zR{Xa+u=Th2b7_P98gJfjOgCFtWG+7Et@^!NHJ%4=_vD?Iw#eh0lM}scOT`ccJ?KJgs^NY&d+9hAjS`t#qJ@H|WncX|S-m7cp1ZC#LUDXO( zyYvbBGV>+d`maXcxYK%~JhgmsLH_RRpENae_DosIpgwI$ zSMdJ6O3|S$ANRT+l5x7a(o!vS;`Q9UJLQyaRG#VyYTa45!Kdy>&)ksmBJGpdIq3!JL zta*2G*Ix9jI(B>CcehvEzpN)pyv{dSt+jT2U#xeZUuo?M^B>Ix+4Bn5ofMLqs4P8Y ziuRV;#ksB*cH2eXUNT+j%bDGtKR@mlyWSPxS#$bpzpDM2c&@+uYM#Dz3#|LLdr9ne z$>8LvYdXC$?YY7i+nzKb z-Jb5%cgy!%#}&6noPlEV7r&ad_1t^Ejb&d}dmYnntxSEJyf;6)>cirT=ZatXE0%l~ zeD-IhBcAR~B&b*rU)ayNU z*N*i)+k4eyd%~?Nr>j_d%*9UKP0e$iE}Id!(93gHOafcIT?4*5yjeIaMxYdg+>m%)7hdo;AuuVQLk8qOe_# ztck*@dg2`^*6fByKIpjZE&CalKj~tU#L?h9qtnwW`Mfj5W=-3>eTl_oUcKBY*Ji8g zdOG>W`kdG`Z@(hrfZY^p1zgVkH7j(JIaqw_fGfa z#Wi9%hr;#zI?aA0N?NJhUUoFN?jQGu-|P>}Wu=ZhHhcRlU5_!%x?69_(k=UToZ8fJ z^>vw#l-8`QmpgganpvIP$v2B7&){z#%A+e;?U7P-AQwns&d@j^|I*i zaXnEj_s;0;EL%%H`kdb5u*dzouC1Bvp?N6@cM`7#JzX9D*pgjt+tb>4(Pba4rOJ+- zTA(fxQnoHfQTyTo*SC8@C-`Qne&3WTwfJA*nd@6KKO`llRbLW)5PSC6?VpxG*0N@* zH}v@+H52;kV-WwCUieNz2~H*r8l$L`hQ99Kl^^)d~IH5 z@~z(4F?Ua$x)Un&C%f@m@!fw*WG?+@NYM-U&+vrr`rnm(_J?bZerL)-0X6N7HuHj)>bMEcE-h1+Syet330-x4%A2lm~ zY~QiOEnxZ8Tkq9frc4f+(rkQlo#VBIt3Q2RT~vDdnAer-`LkAX)@zyE{^S+Me5l_0 z-Hgk*J~7? z-8XyuNap8>n=>neu9ZZTPnvgo+VySgPde6=)>&!imaV$how8SOua0H@^b&4c!&hbg zd)_A7$>_YET2{U7u1<<)_^x-~qju}hEidW0yN^}#b?)>BKiBQs5obF;@uweu-R-Q( zlFB;!+};?)DW@j0`mNjaN=0*Ju5qHx$rTqb%uUVsnX;lw>cr8LM|R##jh?%Dzp}-S zy>Ig}ZC|d}N?-QNZui2^V)BpW;?Ep8clq}6>i(*`uS*~HtUR6<)LW4%8MO7`l(=V| zoC_`NPp$Kgy5?|YR=C;DHJe;2<=*OMsU}zb65-_A)U;vx?EB`-vcZ{|?|$lR_PVUl&#Z#3ZFUs8-rRHAq_qyJcFPDYhxtnEI zm~C2XE?*tJQ~GAnEZ-0AQkUmmd9@>Exy0?3IiV*Ht(>#ptRjA6h2@qNU+WHSS$*Q( ziGnPdbIa9_KZ@AcA2H|2Zf=ED>*|h9i=Fb$e8S;6qbWb>f+r{dvz!Hg3+lv9rG{ zpnSoiD{Jn|Nx0RuH!E<`(qzfi(;i#Ty*;}1lil(zn~GxIK9B zw%EQ6H?Gf!`D1Rg@TjhlP2AmCf0H{yeKNzo@I<<-nRn!tWv0buQMtfHR(I6SZhFgc zd&Mlbm9}}$QukDEIyyUKO43u=30l@SoL?edAOMGO?8Rbg5q9veyjT;!IQeP{Ms~MYUCSbdu*w=Yr4*{)aI^_RF&7u zY1bx;9OgS!v*`MPG?$%hXVSzT{pPScC9qU}LeG)(tgE-bi>@}D@^0@b&27KSRJRxI zwfxn_Hoep4uixJ}InpJMc9s=x&oj-^?kdjG+LWoaHSE&8vmG@$Hi~Ac^G+W9$$e^% z-g(AD8?vSD%x1kN>5|ks_1R7n(XCdqPHP>%XYYU9uuF1<^XqooZEAj#E=3nMb>EZT zbX}#0FXgl1>g;Jxv*j)aweHNB@?_g`jrToC-!6sZt};Cw_4D@2z?aph%`Ee^m(SHa zyLJAW=h1WLthuec5Q8_0f zJo;#QQSPhPzKP2+J!^N1>+2`VSpV6( zo>$#_?vYC;cg#uHlbbtzl6HR6me}R<|8l>)<{w!3U7RoMsjje4;r^+5=QvJn*cv;p z^yN~P>2nVsH(AMctz|}O0(zuIgEQwAVJ?{y=@pgM>M^rnW`-rm55(T=i*>+gkqVsYXRvo>PAA&Gx9cGR5T8 zE5qF)3R9mHW_xk;W;vXgu|{x^u;~i-oI0PZ?v!8DV>mAk8`E2&8(lZ#e3JgPMe(6 zm>qT&y4{^iF89WrUAZeSbZM0P?0G$VgAcqk4>dHso98F9dop|PRFgS#R~Ju7mA+%R zX4dv1!QzKO)61S8mh|d;@o>fi%WHd1#2+bsW2|+kXvOMxPm(Ls?6dphHtqMQQg_!{ zyXeTA*i`qzO?JnfqeNVvDoY)n*g3Vm^6}H?xw~IKP8An_aOYC>`N}VGi5Ds-`}S=< z{d336TRvAqUyCP}UB0pO?L$qsRk~)YLuTciR&PJvlJ?@szGBa5d3UU}uKcN6HtTHb zmpAivm6=X?BbH=0Ra^XUR{Ap8(}ruU+3s|=n%s#vGwW>YUPYA~=XM59*eaW>X8u*X zc;iynYcZb3!Zkl`@ayZ}Rz2x%SXTZL^`-Y!zg&0Pzof`4Pi^fL?h6H5*7%vPNwU;m z{3NDdbf=f}!@ecYEra(2Z2K|qMa9v5JV$oR7I&Oql$W+`?Xhf~k6Pu)lV{Y%_9jj5 za+n$~^Pk~cw9qPrZ;Q61%v+<-t9sMO_{QqkGva&Sd{~;RdT5_%@Y#?zH?~#ui+;Yg zbfW%oss3uqw3wZC-%9h|{8(KUsCfTP*9BMW68@~MzI*y+`U~O3`#0~^fA4J{-5HZbde_^rab~&y+0Ap)Uu?^B z%RVhO*QReuowDAud(j7<%Y|L3^bh_se`R9au^lhN7FXR_yHn+#l}$`wwbUeEldFD5 zbL(fl+833h<{PQBT=i()&0OELD<@3(GVA5pQm+w<9 zK671Pue-PNw;3(TyFKZq#*ULYX|w*^`q2C&UZUvqY2Wy^)auo))_Ngpk~NQLJ$Z7| z&(inJ{Jiyc$1`Wt-4a<<@t=Op&S&w$!*>N&1=;6b~YqPzAYqK}aewBO0c*D9xU9Y>Z zgDx-4j13QY^mccijn|`DHF|t@MRO}E%C5D!TdaL(fBK*Qodd3gh z;E2ZtvpSj+{de|0Hq$+HaQ!N`jzihkerILAoOq_^wBAKa@%U@&)ZTi_3+>K} z_0yVvw0p_2KO!-vAw_OGO2aRmUi{bK&`yb0Vx{wvAFkbFBRyfSx_XrM^#@Or2{@ z-J2GzUUXx2?v{B+Cg&$>?EF6Wi{0!`ZROU*$~(5i+FhD^?wwY+yvXA`!Io5w<(B6P zdxEEgMy+=JcxRv7obwmH6={EdXY(fYa8}S_N$8t;-wLxweN*-+oJ`=}tuKy~T~Dkp;+83k&b@i*fcKUQQ7uIiE^qQYKF#;L%k4ftTcf9E-TmhN z4G-9^t@U&(hg-DD)rzI+;#+kmdTq(iI~BNJW3P49)sr@FdzTimtEb(#l9%u-=H#ip ziL0k2n&#fxnkByIj-J=#-P&{b0$UY!?AWnbTKN6cZ(Ft2?WkR%aI#lfE$?truJw(M zQgyRsy@e|aZ4#f)^q#xKZU3y9VQjkD`_Kmd8hI zsbYFDvwzlCy(BMhQMYR*v1VSrr`{f!-^LleYW<%@>l6F(x&1h;&))s;CTI5E{-s+T zgHm=U`DC&jxgECV{)X>{%?E-+f+Fh;CVOk@pKwpV_`r425?%f8EGuqLs!aT>w$l5z z$!n3R_qo?rB<#KIw|YQB?i6!OPd@&4L%UzfVXuJGg8f2o9M-P`?QlGrGwBL^ zhhu!{8NG}vo6?IaA~(-@8fo|F-StPG%T+h(9o_KFd8y3B3vnJ7K5p2!bNn(j--ENi zTJJxbEdF}`qWf>_wf{5R+hL!saprx`oe5WW{GOS1?(w^K*DW?1Ep{wdJ@?hEvLpGe z@2sUtGUj?kTvETDcum_f)8yt3?}8I2Rg&gwyInI_TmEy$OtT!d=^}6a1Xtf#t+6!x zMqG*BvM;x1m9j4Rbl%`aRo`ljQ(ouv>hxT%ORPHOb$9jV**#%5tpatQE?vB2`^mgj zhG*)wr5^p}m>KWs85ncuX++fRTc^`lb8{rl3eA~lRC4^nWe-oOX%&93m9MVL;buBNd%}`QeN#_n9-Y39>nclK+)U1+ub;f% z)yY}1f95^?efC#>-;?@Yli#;rIZp1~{?p5QE@xI%mnd0%zIgrd@}~6#)nAjG@2#`H zv|{&b^CP<{ zyN=A*Bo!n!SzmEN|N9$zd#fUT3Ks0=G}YMZHtWV^w?{9dmd#z4xy`g!>F=Jo`dU}= zK*u!%?99vbGVYmnY*yytCRx`C$ofF+B!$sL5YOMA|n2EE7yknl3 z{B3r0Us!Q!#h6Z;}g$7k2IffKlh~` zbEcmcFX8_eKWpw;-EEiRZt=cYg zvt)kp)I6?<;a7qu?PPW3z8Ex}>D_bArR@3XXT2Z&et2?+g8TH~Q?>amzJ>W4wfYNh zST5JqUh3K_7yZU7?r65$$_bOQygd1jPE}KWH|cif)g8AxeeQRj`Nfwa@;2pG*FWnA z^)G|3hsGJ7I+ysuJlrx}En?>{vC^BmTaUNzOS+s}f1lOeLvEC z_`B@7qDzNo-HDzkxm+sOH!$C}a7x6id68z#d5a}ai$*xkHxnfRoy4N%6H|2>EVVw{Ys}^zuu%>b#vZ~EvllXK{IBY_WLsJ zP}u2p$+MPQzqqFBb^mj2RZZ+^&0l+uXSXG9 z20u91yfZQ=Xu0Uhqd8qgbtRK-?8#4jr5?A_dyZA-i#*2! zkFCYNo-DjKcX!~@)xmRBO`{5qZOXJ=d-d`Dhd!;ETuwEv1{oUR);@l37{VGr6vd%nRvs^o_PWF6$?k@EQ znQtFX`f$1I*i6n}4!`U^osan<5qa)EL+G;{m%ndka;)^@d^>gL(H-BnrF`3+7r124 z3oqVn-_~EaXDKIdJ#C#-QSXZP@}FIerbZ=N?^+jT5FqEXEMdovnMYJ2U!V5SJ6SR9 z(BzCeTV4Om{BiNO-ol-qeV^Mh2X0#~ed@}~ZRR$&>a}y?*GJ`heBz(GS7O)xla`l{ z?Ot}?Ud(7o-XpuO-l=shKk|<*-FWRjzxZrPuZ?+eCqD8#Fo}47vV3-pXK>-o((l6Z z6RX-%S{0{g9$nfo(dheAuZm0alWu!NcZa(yRZDZxdmnvq()xWL_pRoCF1V*E_R3CA z*P?Gb!(MJ@*VGT)?WwPQqr-H6O|6a9maR3%KITp=-(PfXvG7F0THBtRH(V>fPnLPO zC@g!@=Dvr%?(ub5HIMyfs#!@DOgXsw?v|&E&F7TZcJ}4=!x3f5o-RAZuAh0_<3BZD$}D=8Pj{rU{#>b3r-E*l3S=)fXumS;@lvnC zJSUmw>kh}CY;#%25!3XrtMB^ZeUql0)3UmpWtGU-?E9 zpUF{ekrRbmwkOZcYT7X6*0S*J(RL+nMmdbz=M)9H<{o>aeQo<*`AY%I7H!oka<{9~ z+_YtGcvpqy&q>j#Tz|4x-{!k|YunOLmpy;JtoY*oH!;iHs{QiwZ24Jv?tk-+?71E3 zr=5CZ$IbDrUb!z`y*2Zd+ATYsd$o6N){HmBV(pVztt-Pq;!k=y{<^f-=G@g^ z`d4d(wIXKjVq31gajNv@q)Nd%^G-fqJuy4)*QKq|m%X3elGENN^J`jL=s(BPf0ZX} zmQq@39=ZCn)!DtZhH(>)xGi#Bc1`bT@m}4ZMgcBsDn)1|B(jVXJoV%-fRTD?EwKlApse(qyA1$`E;{#vwu@Y?r>ftTlAn2)uj2`CKq|0isNUBot^dV%>=8J7dgMI zUOp>t<;t`1&t%>@9(waI?$wp#w4mPExl5jJ+cfvsbm73juD~^m=A+zUnGCTi%G`zEza>l`APSxWi^ipKAP>Nz4eXD zzDIIzp1c<|Tcf*q;mh?YH;!bi{X>)BrWz)edlz2*Lz z9m`J5+%_p%>O*+ze#Xb)0k(2KPfz-)`S|7KjjLbQ_&0sCnKZdyIPksB*_Cd&Do@t# z-q4l(ygyg%am~4hzt?ViRT+Mq$-Be5Q{B97OX?x7OI}Ka%ZpO1E}iHHWCq5!>ViQ=T!}t34?zDC|6S#ysTm zSs!-!k_$8T-G26y#p_KGi|b0S_qDuX!4Y0OSH({?g|4@rY4XQAFKoy9*q;*mX?~qm zQ?n1G?^$_KuHc!?j@#$omFy9FslC(a*2?{DS1i_f{;-&$bo<-ym&@1dm9y*pe7Q6J zzD>hX{g)qix!qZPUFzSmZ6`Lot?$)+@9pa=t?hK(?oiPL|C=}W-kNUvL~#A?z9SVM zAM#~h+v@B0d3Tl2U5}&chdF+Tx9;z&zTc@beN|omU%e-GE|+s3WbQPRnKR|x-JKSz zZ<>c4)m-&+LvLo*to7`&xW)Oy!}1c%{qn+$U-dKY7Q%z-$%w{q)b=bouPE;%`n@ z9{IfVe<^!^&-1UR-MaoW%y56>&HVT2u|L8#x9q3QlsxYmQT#+9z0gX4lHDj)>S=d-O@gCWXuP7oR<{xg|D9otLF{q17v^WnxqF%r`GT zm{s)9ulMcZ>WtZU=G~I>F3MN@Y;XT8{_@UX+0-nFAMd`b{dnkh*_Fp^kKTC+=6V%= zu9#eUW2R?NMc=*OgnE}%h++( zA4%Z+wrPJ!kJ)%k>ivotSlN^`?OB z8v6s^RLfmD{cgU*dp=&pV5>v-r%13J*w8aYP0n)ZaYb$`Im3P1jx(W} zQ)X(uyk_vou()0njIYyxV^7;jtCax%*XPdh8$G(|?^EWJ* zYPeH!-SIk}^`4#P_FEb@uj>w#J$9?iRzEZkn=jfL`yIdZ;bD8Yu=;PO*y<4}~Ij3CtGda>etMuB8FW={+uIAYs8@cbwjIH1O zoZTZ$-!E8jbLAGH>yr0>oiaY>_uOY*?BpZ+=H4|bTF+Z{@Y^Z=kNJ6gY(wekCR_39x zHB*YDWv*=B)Mcdp@b{g4Tf;+)p12n|9@?~HfB7Ait?u4l&YQH~r8{}g6}~@nEz8Q& z?6XaHerG-1)8V-9xU*>(A=mR#@o)I962T-DxFmfH=DOLFGSaNHGhr29n6rIJTI z3A?Y%`zk%{p;hj~kdpo8amw@LzlcuCU0xjD_5Nt*o$qUd-q&VMid?^c!j+UNQ$6cR zQ8B)6c6^ljJx%7_ZB>hXr-YV@)@(g1CHSba;>yRE=DJO}Gv>~lT57KJYAavdwJCFJ zw~4tZZw${_o@W@nEp*l1?re`E;>LSFPM6#IRrHrQjv6P3QCuDn|Dut_2+x@yYm7r@2jrQRk znms*zXWsKWk+#dSH-BT@v-{6<N%|GY`Exn?ureM&2ICU z-PT>m*|>FX)VIZT!8_G0PlZmIpZ$;Z#!Jv)OBPxBNT3yuBx~S)a^}4o^3A`xIOu{%EmP zez)JoS+QGkm+j%*8aAnF`^)$Xcjj&V#JjsnW+7kRrMz?hlx8o{J60IDzh5)(gZ0K9 zRnENkx`&JlTb5e9ecb+I=8x)hkwPP>y7Z6xcJUlBdK_pRyRqYSP3xjMoy-oOH~vYH zNjH77S7rqTZSiQkbgN|ArIUKw9tm&QR@v;lKR0&!^!@`^L(?VpNBNq*T70b5d-30p zOPkdjr=PyH`^0v&`Q_bZU$ka-FFkFzGtX<~#bdsvo@$RSmzh^oT>X)kal&Zs>=S3o zGh$6<{%h@d@#9n3lHcjCZ}&Xjxaj`lBYU{GmdG5tEPL|1z~d9)`MV2aFCV+B7jK#o zQMhLPu_xQ!Y=3@jp6RpoqEY@=Qh!gcV!YayvTJd==l;&5jcd>BO4e9uzV*;keXl8b zUUyfX(wZ7qlD(Ae-#v+VWlP~b+wFEAURPkFo$~x!&82q#TRGnK5xXm>geyxw(L7yQ{Q{8opX7v^rX%1*_$?b zowyXVUh%VAYvbu{1xD{bq#g<`>SsOfr+ISiDn7lHYtMXsed+#!y%EpOZGL5Q|Mai$ zeU{~V*Hm)jzF(8ExVy&N?491bXD)ij+X|=Dyij==anh69zA5Z1a9==Gb4IDYZE!af|VsZ?m;lF5cgICpy$4d&!J7D`W3xs$>L(6h60o z`uKTvxzWeep^iTGD?_XtZeNlaSXuIGg zkDuS3^5C_k>(5OkYvrz7lwuRfHLSDPIP-DTspZ&c2mzU4+8>poMq^l33wGQZ`s zHm-?Jczk{Owz%t8UFT+6U&wl}>wU3g`x5J$Z??6rIjLTJ(Madv>xjxq{&u-*p({^b z+~sDjtZAikdy(%&wfjj1daE*a%{MAJJmY7%>(=|(lE3&C_9opIQM?zozq9J{9tiKDYIow#;``-_q-}M0?>M zA!Ef&AD`<*&k1=vIb!SP=jHV?Dv~F@ID73+^rz$d7Zu!VUL5!5KuPwoO|!nveDQma zoyh#R!T%Y~Sx=Hb^5fTk28p8QcjZAN6D9xN?#=x7^gqMe2lZLW$cm2Ql%)bNB46BC^TgdOukE-8U|GGl{R3KNm&iIV z?_+ajzr9sG{ZjX=?kBe9@}W29Ci9(LxwgM8?5!K0X?I~s=Bj0v-{gwF@1C>n&K~Pq zp}&)TVvWTvF1s8*De2qGIzQEU$z8SEpC(88R`VM?z9<`=vQ5Qg+f(P7&TBDFyS~1E z`nTSEdhxY1>CbT|kN%E4e&G6s@5^^={W^K^pS9QOg68XdR^1ibTzq`Ll=hDAnXcAF znQyXF9=w)3o-#Eg>elx@JKH^qw@VEa^1NTi8NBIxaxt?nC&DajN6gF~r!OZb8TQWZ zzvj7Us$VScw(x?hBG>PEE)?`%o%ZZNrp)(+S!I)V+CBPfcC31}@0Lj+%aa~G%s)`^ z@=CtTzBNU13yZFaXI?m7w3cXU9E19q$Ndd}4I^ z?dRf7PUp8~pM?H1D3`LNPihzN_OzAqOSsbUZld;0#Uo2I?3FrtdfJ?SymqUu{oeaE zqTZtIzW1-`Vvl}S$E>O^cxvCaHt<)A_uYAxb<$RGTffh~clPb+D~5_MDwa)Mu=VU4 z9b1{D>btTPqfM>;Nw4?l@m*;2E_r3_D;Xs-pF6(0^=FsJcwFBXuI_umFZ65es;?<~ zlTDu9ch6h%S@bFEGJkH_lj_BKftwabvLVcMto%d=y`UJ`{py6 z249ZZum0+`e&Pgeo|xkdvy-=l7Z8`mCqT9M=KGrQ&GyG+3G?tyof%UrlU zn6G%han$vP(tZNlxqWxklze+rTHCp*a`o2y68EM5y8d#Xf5{j6=j+so)2GhQ5LwiIFMm-A@&Y-(9wCbuZ^_)-88teb1Gt_|3B7topVGGj@v4{a|v} zOv2c1FD>NwE9D^SiuHI-YcQmwYcB6jHRbFZseIrKR~=r~IGHXFoKb*G@n2 z`Mb3j?bc*$Uz%*}a3$5kWPWkxljWhIp2@p6PCLG>_ru;vi+#m~?jK2S5Se;uty*z% z*Lt_MmbKSbncv&6e`8eGBPpeBf9J`O(SDJdDQVLp3O)TpC51fB?0&i~<6?}zzSKPJ z3H6*-(^lJRzsh~xb;YteDyUwybt3DlQ(;%aS6%FR7xep)pIGLv*HN`>mHg^u+OB#s z96!#cZc|;S7`b8j4b_;mpSCfUVF5y~gtl0E#%IcTr^U8kaKEk1w)DJ|n~c=ayuVK$ ztVxcSnRRRLsi~gTlWIkG^`6-&_I%wTDZklTX74QS?tAp|txojhhf~d0U*7WQ`?|wH zb`C}2b;Z?1UiV`ro{-$U+oCt@am3k>^i>~ajGC<1?H86kvSRLZ%g~TFcAxGB?)uVM zG`}$4qw@Kb<$OZV>{3r&UB7$!lBK1qa&AhjX9>HXx}Q6Jm8|HCyw=NSweG3SPYTx z&w2415vEu6lp7v;pKTPA^-gF~PX z^`U=mvdWdkDoe#?`P>Qax@TSEaw}qPY`T)siaW)(3l<88U)VGw?A_}#Z9W!n=B(c1 zY|_kDn`0q-ZF7)9)K0!)y|S74mnO8FC{O;vS9|emPR5>{Yih3Sd2wowY4n}cUHu!a z?9T4K-*GqYi`y%If79C+p8m<6bzH|Q_vYU8_I-i59}az;wX*Q2zE##j$;R5^os zIzE-JeQ`4Ue#QOJ_Rk$^wtBB(o-Ei|9kaURcJq{J_qod^c^|vwz2(Sxi`oz8rM8Nh z7IqX0d0C3?Qg^9R_5E#X_TkGrS<53^roGYEQ=h84EbBAJ1i#IEQ~6wKE^NCuU()l< zYN>0M_x%oS)yRAscXHYBt(%JiFKWJyoC z*3motx$)~a&IwjtH!*nT<>c=tN_U?r{eCjfC6g=jOPO`1Qq;!2y8WFq*Ls_C)jqR5 zDmGVYabRH9B+vJucW?R2wf}NA_;lX;&8Ph)Nqc;gxt`wGVLk0#p;P&~*p?}4y$Y7| z9+lEGn-iPyO{2>oIj8TGoR!(*n3{{aR;^oJN99MDJ4RD535%G;d$;q zL#kMF=+5R(Ti=)Kr_Nsx*Z1$-m;HNn{^qGKi0rgESCNAl zetGwD^3<5@ZM^TN$fx(_)v0f}Qtuh>`j&sKnYh8rqCS4x+9tnuW^w54i{5iCpIau{d-qj8 zQ8RJgQxMEWk`Ozdb(1ex#Xt3;i_+Df381XGV9|!|IhJ@ zkB9$sH>%jV%goJ3;mOo9uJ1d}x?b)wYJqZT{iQM-720N5_hmICf;xP<%N<{LA_+{&Zmz9EA$-y zsJu!t+ZBH0^mWk{Mc=k8xqmz{x~1kb^>0sGn{{vUa=+elU#6bkV;cJK_*Qj2+qElVr_MdM zsW-IL@CmzSnZD8XZS^N?yIUVgm@dnDo1AkgaqIhoEoXB@*fLjgoeDYr#DD*uq)9Sm zDmUyhLgqcSopv)*BXx)LtgjKTttOw(65OjYIq7RajeoVyK`r`bk-g{-<@85K}Dwzhfmv8-&)gJU!KRK zZF=T);=}NYyYf4dmQ2p=i@5K(F01yJ*1Bg`3^N`EZd6l>_PO!QW6I`l=@+{9Pbyu* z%O3gtTj}n}-P<0@NmZ`%4GYN)PYwIJ_q}f1l)VPe-mU#|^3!_Fr;n2E^u_M!KJ}lW zV(O2`o;$gkw|nl?RXnuXUF3VnI(h5f4PX0vmbdM@(kpuET+l;{n&(fCHvILw(m5gP z%XHWgGEg$EZ zy`Ibb>bw5EvdtzoyQlbGJJjctSs^;p&+~_}#>#ysrvHpnxWnlw)jxZW%Zo|IcIQ>L z-0EK_9|HU46i*--3PXLaQIx@n-C) zs>!vVP6H?62H( ziDTLGsvNIA{!%c>cf*8*3F>ZRWacz-!2z3B zzTVefve@V4wZ>$2zfEtGBAXvRT3%UHblduRthmUt?|VD7+%%mGyUw0(x^Mnt@1Y-a z=FL5sJNN6h?s>JTe@|Jz(@%V6ckB05+jZgh4l^G4G+p5ro}S-j|Ki<$27}M*N$kG0qZ)SjPvYtMCG{8Y{!2ifnNIr8aP;}n?B>Be30;@gYBI*NXYGRc`s02Hn~l(tm!sagn=4QR;t&>d$dA=PtkVUX|tj&AB_y z1%5kq=gAJe;N(1^NsIlqWL&mC_HWa)_9NXXN0zr7QGHVNJUsQftIVVpk9|{}c0~q@ zUE-K<`O|{HW5IJddXHQCX|I~L*n)rMjcIE>GoRSDQhVBir;kgP)~58w{bl~y9&n|~ zl2_TPXlFXNxsq7+$8C3}o@4nPH237z2mY-&o)U`$ckX@wUt&WZL&oG!-MsTF&os(j zm7iPNMh1<2xsOs~Y{d&$8^<6|+`P+_N6Tg1iwOPOO z`Wfs043ePJ;dNe5{F!T~_c(v*oHtrrRqxt`_8EKW8=c%T>tDr*Gr@OG?k~T6a!XvH ziLW;6b+My6P9OGNu9vB->^V`&r>AEhh-Fv_%37kc#4;C;8iDMV`F2p z;45eTEKC1z=6Llw@@I(c*$e*}7A5>=_{7q_Z&RIL+>Sqvi~gQX ze;Rn&=yc$1ZymE~vm8sDGxSac#Vr)%o9TJ-qvzG>-eGGawyllq>fO3i_r$k8zQYe@ zhsP)N9l6otp&xogAP?=uxSwF)ry( z?M`=l)(HxJQ4HCtb7aM`GC~+B|oxW zKa~wPHp;8cv=Zm>i@InqtIY7>k=;?Nf22B1S*qgc<*BJ}6y)L(w^4jTtJ&YD?+kmw zd4fFtGQX(&)BaRHfKO`O@g2*bKe}S(mNh-*(6sf_qJlgEzy7cdzI;k6_vw?UZ<9)N zX71U!IDNa$Y=NTMm4SbhZ#%Ad5O-rUTeH;ET-&RvyvMq3Z{oSKZtuK{;u{<_E~U6F zRxMsdOPwaelhD_bMn$(WpnBK zQS*`(?O4oly?Sw8-J;Ls`L`d~oc;3kmA>}tY{w^YQM2ZrvAlok#KVxDipP2PtnPizQdRG0X$HBNV`c%QSxwCc?jjceX3ZqIwGTSc3YwxaY09LaNiRG-1qB5wJ398XI4{|`WYVHL zh1W#3d^>t`>&ms8{nIzz+|VeNSZ1ZO+g4@E!?G%i_3E>4%CAaZSN8VKO^aoFGL4qY zn;Q#W7khi#|Hq^$HF9iwxUad+Il1HEsv?)QuKiP0ww8B)SrtB~BfHdhrAG7{scDB> zT32sNndG=OUMKy@OUG)DgL^;DG5WB!Ep=I`{H!n6CZBl!R;zNm&{WZK^^NP9)@E#1 zvPk`7wWDL+Z_X2u+x1qx=6b2Ie!`S5#-3`yDm%`VsC;>q>7MDO^kR~7z?S%@zbD@A zHPsis`QfCz$7?a!>j&39-fe$ud*u45CD|v#UFKbT(Xo1R!p%H+#llZk(o%<=cH4fw zc&#w>%~{PiCp|UitrK@uRZH3IHL2E0W7kdnvJINysZ|oLQ2^A;szE?A43Dne2G`_~?GQ zODj{`k{8TQTw-#orlswYr)QlYm#0@y>EzW$)?sTSo3>|Z8N_x5eCy*oz`$U!^2^k) z)c*{zO^2WV4Vn)iW$vtJZtYpF+>4_&(-v;2JAqh;%^Z#{Z_ zef-<*m#=>A`tm}n9#UQ8PyCsCx_ig%cy7xVBB7%DA_9~*tykZycH;Vz&(+F$i{?FZ zeZB9V@1pOmi!*1{-ViFVA}%VU%F-0>Zyq{&Q*R&y8hItNjU8wp7`Zy6RcylGxch%jK@D^SruU zH0(UQvQwT9sRbW@Wb6KJI)AG3oxA+M)cF51D0o!AR)6yJHKFRUsBn>6%SB_+w#QrC z(tNi}^71%;u9lb+y`w%Hu@I>@b_kvquyk@$qrweEUa zU1`?klXez;+3~b*^`l(*7#nMumds$&9?2!YChmxRIPvGN-+GH5Ro)CY^U7Iy^l2=+ z;PPxa(Ky+CCxgsx=c-J&C3K7X%oLSLu0LEav>NB`+WYjM*uy*i)KK zd3@I1F)1sie!Lx7IsJyJ&fZk{U|Dga{JY;i<%;*%a(f;uJ+?8==) zfqz9ssuGG+g~ z@L*Z-En>o&wU0Q$La=LWxwJVobc-@Zj)pqJ$6*Bqr#OVi> zWi@x~TKn``*~2ovyGQ0oE%q;VKQ6RxAJdc-0XKTG^}5XhJl={Qe)(L!cz4$#TtRjpcg?9-)flNL#TGrKPQ@aom;M;&*omG-u@J#uQ7-1KS3 zpOyNm9R3G`I@-_fF5X>awszS&&n1)Y-M%q>)wwf!_T=z*@4UIWcaOE%)1oI&UP?G| zo(js}=%%^t567E*hqa{-Y~7~mo2$4i>aanT)@jSQ;M8RwZ%J%S^Z2G!@q3Ay)YK=Y z^PN7=xKoo|_x8W>iUwNZ! za%uUBQ>rPE);>FB=SdLL^dK=d(Ww-ZNUBB?;V&0icC2t+;*WPTo-@10+PJg+*M}5w^$He;l`Y9VJ ze&iLXpv-&p!?p91z3$G!a_O2i&k~bMratrPj+N!sH#*C*w9-y$S6}zcNb%Y|bGf&v zH7-fm_4vlNCA%}jic?%%FLZQtpKytDQoIm*$vf!|Psizn(}TY9pK{QQ?JqpyvtZf0 zw4&9Itrq{AbK6t<(L{lFZLYuTF8PX`Su5^!w)Eu2yr2&kLmo_BzcEzfO6k!n=U09y zOo?xat!Y}A71^CGen@B2le*|BbLRH0KXxvNP1`vtGbHMn(3U@|uFd=CQnylS*H8D* zGiSPYtE)RbZarP4H}A^EHIANnxuJiI7Ah%y5}(lhKwz1Er|QN$TOnolX_sb9IwhZ4 zYwf#Z-X+n?XRq|1v+P;Qew5u)HvHq8C%4Lql$K|{_s>cff0x((SD5`j!vvLbNRc+} zKG{wFCp}fYTbCYt^-wXxC@5rFklSQ^!->C^SX5f_2A&J~G(X_Nm+GS6Hq)ghujX@U zSs0|L*R~1@O3oB` z-IH46CE$C`uTyvJ_MNJ?ZWipXJEq$CgapbGbcs&RkXV?(ZwlW6{>T3w_sVy~|q{gGE>+2#1uKpZ`RW+^EK;CXU?n(=(#&jeo=Hgi*D4EtaaXdrpPSR@O)K! zdY-qu;m^q@#v;mU=#Mzhzmu@0oU+9apH<<&B#!%Db*}+igYgJJy@} zZhv?3&aPFngTkKby)*JZ*Q4|;PiMxVvkS@V&tlWHDyyL9*w1sXW{dCJ7@u;*Rcu{y z%Od~m^{Fn`Q@!*K^)Hl<>ooo>_~qU9-ihTe3MPM2i`w+{tUQxVBH-(k?8;m+WqOd(zo%EOER_@#EH#y!C_d$Q^1(A*tG5=- za|?`qb}wSLY2Lj$on_xui|h~oXSfk?X~Eg1O?f$%0e9^73TjzzyjIJpW!G8BVf?~d z`D@SKKwphhdXs$ZC2P0V9sPPVsbEfxVdii4Qx?_^3=9lrk3P6O`Zo1^h3|&+w=&ML zxk|TpyB)l}rdn~`;*)chEjqMdvHa0RHxEvJIcY`SjBQ1aYRq@JO=?Rs`>f`YSr~lY z_e-p``m}fF&F;?r_ha8{NsUtzn;v#$UhCTvI|Q%?8Rm~C%=-@AI^T0zlv4ZkggLBb{y4 zbQ`YU+InjFqtB1cHy^q7pFvgmukhv5>!bWcHX7$;N=>~kv9w%WaP@?xS=aaGJ&w)` zjfp9Hw&$wLYCY|{Yiv}1G_S7aSi9s*5yy!yc^maVNf^#5zj$HEnkDuf7awh2vMlO1 zpOv4@qM+N#dgn6_#UAILXu_jAq4(tRw3D9tGkT)e=f)p3=*#%B<<)-%ldDheA3nb8 z$DY?=OKz=iOo`nu@nO}9OIn>5uT6Y7J=#7usEMdc~KmTRzYDCLMTRZP||dnVim-=2!W2Ub^D8q$=llC;Qz=9EVvL3`?No9zOb&p$aP=P#oR}3ljdY=p6k_~p6;iq@>|GD@b0stp<&C-3^!#XvW%Uy z)~VMWeFwHSiye|al38T3ExLATsn_INZ+1*pSu<(zi$yP%eElTJKl6~d@?n!XHR*L` zVQ#Wd>&1$;S6R=A_J3GbvE3;1hs(ZKS~z*Vm(WGQ<%@cL{p?7Y)4Dw3*fZ{#VrAh{S#xKn=f3%ncgJbo^f@nAzn&|9 z_~KTxb=pMV3ip48V0(qbFJC`X zbKckVpJD5r{Y0(v+_wUNT`fsIe zX-D?1-?sR0QO|tArMoBUiyeQNvGLUt8DEZ&!dN%g1Xn(q*fkp|H$4E%`M+O$vM69 zJx|#8m@JnKJ}L4#_UfXyivOCv+z)ugZkOb}bIQ(ow!gb?noHG)wCq@zwp-i! zl9!F<$AZFhQ@Tw&k1A+wYucrCdefE--jOvz!Sha>{-qrDS+9Jj(X6|{_xqv_O`E^y z#<59Dt_atKZ&}f5Sc@glcU8F;K9{MzEur@8y72AwXTE;@EI#GkzE*3#K;eAnO)EB~ za^>bSoYm$xb$)i>k&+Wz?NlGs_S%a~&ipak(5~vq zUGWt$i&qrI8yviI$&{x&E7QtLZ&lbk$G1z~#`Ngj&NW%iv2E53&vkV_R)4xbzi#&G zrB16Z$<%zgc5nJRgVUU4Mn#u;6;~F>od0aO@xl31UVFd&&UNh-TesnwN8yb=o6C92 z9IsdYRt`V@?y7FG{ZETs+uuFiwB>`!^j6!pSzqQ~WOjRbZ{z#jeN}DSb1$aFb+shS z%G^_}c`~}lKWC}+=IF&1N4I?Z&kz_YHjCLnNqy68PbK3Y+VNjbhp$RYGTmQQ{?2;x zy|Yg*eYsPs%_mWDYC_l7`>k_kywX#^5`G9F_Wr=L{C#Rxt?a_GPR*Tt3muOf4_NRh zc||~>;S~*w8LvcSd`h|a2rH;9O!S+xIZwngSVnJdpqTX~u~<(py_KolMz1`Kzjpj} zJd>zt56yqtMK`%Zq&0iSgIo*?-f-b24Ya<4OKoZw1Yn zdRP2OZL_LiX`se#TkBYk(8GRbcfQR%H_y~R<#^G~>r+46SzK=|-)Gwu+ycMr<7YUwH)n3$dVoIj+oB6@|>QOshRDQa* zXwTXjuNiOOttpzi_4CeqJM~3(>c@AtKMb$Wj50m!bL3Lmx7eO-`}VnPIlK6z$rru0 zte7;O{`K+d{)a`mr*E>(bK1B5gIHwge0Tpdhfii7d$9kp(aQEi&w3|)J8WBM#ObB{ z#Y$wysfkZaR_y+5cCEFtO0rt%Q|arcFJeDFz3OlE?!HLsOlnA697I`l+@f-p$jf+G=ZIZB19!ENM-ZFM2JRG1t$WUmvd@e@H%idT!a>sWsg{ zyednV+wM1JIu&O zPwO9A*T+W2-~IM))xTpwn|`EDQq8uooU6R{TS)Gd9mY2zwO8%tUF-A9DP)qr-)bej z<=1cg+H3Um_I~LiyH%;zu1A?Xn_k~q>%F}#ux{P0s{1@ka(47@NwU1z^vyEsW*Ntw zw38>6r7f8?CDY}^nVH-Am3+>ebX)vM<@8N?moG9m_M0B^KJA|>UX!=()6o~#{uJqY zA9#55!(;WW?>_`@+s(yxz5jOI(#x55^p2&wNS!^k^w{Q%T`E&DRF71iyYyR2U#aS= z@r&3HH&<_4Z9Bfc+jU;{3Hu*?<(^XX_^8b7g4(Y2uf~bGxt2)pH8`v@E4!*1p+u zMQv=l&6RcU7 z(^Gt--MnCvC8xEIUoJG9^q{Lac73+#=cwoNlCP_(PMoNb?ETM=_n667v+XzT=iQwh zSvFycS&P{%f!n!DHFCG8OlVPL@$?dy^2IA?%5*2CPl8;pR5#`sYWfA`1(i-N&HXkn z)~Q{_=V9K)Q#^gf6ZJD+uU))j%EH`p9cTJhIQ3>aYG#@)RRJBj)p61-F4=Bxxz4R! zJMxZtR$Pp3UHM9?^k_BH*VEy`Ym?RXwton$;Aq|SV`k7F^PH`>&pFIG>}T`V=K0*I zaz!Uj{NAyy%=g6UAfGcGyFHctQ#I`_mg!i|65TFmrPF}zItuLlJK($zpSN#y)q9e{|sF+B~~W$(5Ho& z#!i=?dd`#&?D4zv_T-_=mHM1EkJFN$-Heo&+E_38C^8kzr+om_d_ z&lvMCTO=9Hnm9MnQuUnS#1rP#ORWyx3OdthvT*WR!KIsmg51(lrhI)7`EH{4;>sJn z$6iOL%=Z1Y;)>qlC(bMPTRf94d!Kn>Ztl%F%GL$Yy78TVo8nGPcoej?TD3HMO6sw~%$ z@6?adzv*+`=f3+skNtKQiBUHn?5w)=bzzzILwVW##x*|cA5E?8SL-tgu3bLut(x%a zk0+1hetqG&@pwVhZn-xn-EZaZeWQQ!mhubdcQH3tcn02`zEWa+lE$*lFTS32&?E-F4a5rry+VakGxio&LtAVbj_3n?B3Vt-tA~0@!Wx) z_9?qRC^)a~I3po=yH~VDNGfy5q@XDiOMb`|8)i=~DxLP(c59xP?&`2f0kQ2juhd+4 z1JB2JYeeH|t4~6R1i!x&lH+}h=EG?}q-Q8ul zH%mNOa>=DxJy#}83d;1Fmt$%e{(xF79bkaX4T?A=4h%lIm1Jl(os z?W}$!<*=zA_1~>gcHWd_vOU`Sgrm-ct?KJj-Q2_HuZ+F2@t#@q+|&M>f^yX#-ugb{ zz2>JdcGYZwYd`bu>B-fYSw?QmyR%ek*+X@SWsh!!#woGC^RtV6-FNq@cVBGM>F8C# zFZ2Ix51JJDO?uWOyLAh0e~Wz-d{OmE|4Nl>h5HsRxpU;_)BRS*561h7pfF`&=QjDUAE0lQ)TtR*v|a5HED}Jr!V67GQF3)|BS^g+xfMr@pFHltBkx9 zytgdx>B${7(^-~knjcuSV705riVndkXKw6{wwSxQyzb~}vvrjzOXlq9Hr~7W#9A5W zJd4MIUb$;#>TQnaxfkf(b8(SPiG1P~RTUQ3OI*I?gs1B29?NcCFM7M$@bPYqV{&4b;&$uD86J;aJ$WscpUqPpSCu)M)4P4x z?quKi8q6!7``y| zo8S8`&UWp(t5NETAJ<7aWggi*?P;I4Q;f33mcm*|K9iJ3A{1i-xw6)Vf6Y+ zubS(Xch@TGG_UWBVPaoMk` zjNfh^ow>1O%B>4uk93OZnLW-l`QH2ew2Gp)@moouYL!LGo+?LAU7OsPT_Rj(yNq}K z6~El`H*J>ZS%>N_Wx4#Os@~G**Dc!0_$6Mm;GH5^hu@1TiM)L_qx{G+w1nrYkD7kn1ASRSyA$vrQq;}63kaV z>zv5-Igz>iR=nSBf3xY);i)c5zD?K^6nZnr~YWmFdpAE4O&`vaayoddq3zJ;g$yljWVt`dXc_)taxbZ;I8-dyshV z+qcK@s-W^pdDHEUZdaun~ zdEYH{7X=07OARjlT=HkxqW4uZzr4s)GO{kc%Pv&!RMhvw% z;*)px*UQfHH$NQpEjnwS%&ns*7Vb}<9DMG$^6Av?H=KnhChA$IwTp^g^Ig<8Wvxp_ z%`3a+xFFqzFyCM@Q>*ftHN{Q zCNVEwS{B83y+1cKuS=+2_Q9=h=6m1xZRzK+zfz^HdSklos$(LrV$!6Sl$8Z7wwBHN zW~~(>;aRC+uDX^>+5VN#qQIX#JrM>br*D?qe0iLjpIf8)SoL4t^R?#F3vL~oyZhFh z>oYk{TGdO1+!ft^>T+ybVBwph>7uJU{aqP51QIvgDVv?j>2gmsubGL5{Kd@Wu$X=J?<}FwUi0v{b zJUZX?M{|w2n$EN|inWvv{BW_;tpRew+!jV&pFMZZCfBPpW|M6>=eV$vUU3Ze{W;6sv z*??fy`u10EURzb_KHeg%vT5DHIWIZCSa3RbsR-|zs=T2++H1lp?}4y z?C?8F}~e zwl0$``?Fs0o(Q~<<#qO2inrY+nJDoaFP5FvGHU0Q6K%9 zE%eIUjIhw9;m_|ZncunMU1;cySKsZqRL!*47o5@DvRLZ)>TH>Jmu@}XD$6zNd#YNi z#hay*=9u9 zU6TqYt%3bRY0TvWcr5!ZQ5(pqtmR}vXYWR`Mn&atbe{Q?T;1ZF4dX6 z{#1}pFYk^;Z-rB~F4?npUQ5z)X?|s`7_+2XAHL~_Y|O4Xx%kuHdB21AN}S%Lo%7^n zl*W&!yHV;7)%WdqbWXlkNq1UE*2#6Ay1NSJD`l-xzRT%0<#=wErB-H&o0n;c@~gNL zZGoMOl5Z|~|MR<@kb+c0u{tK}?3T4NKVI`$>9)vQIAE%l%;Yx{Xa2JOEBee>Z?5;Ej-Fjc zInHWl)|@Yn=ifNJmOuRDg{|IE(~OtAJsEyn{O$Br+iKJ|tvH!8hLVawkxdXeWgcYbon!L2Kj zP2R*dO}aJj@2l0R!K+H%x^r2by1;-N81Wxk&pY?|xmz}$6|O|aEkCaK`gB%Q^|EcY z@8Sd1LT2A6PCHVxR^lA@^evxxw=$+Y+_g=_%jnHg*DI&BpWgGo@^jYJ+c$(aa@?Ba zzh}zj++|lS67AV@d8=RhFPX1gqHWLa_w0>twA#OgX-`k*^Ifw&am?(g#7WBW4Ft4v%4}b8XgXI zn;fX>mZ|gH^$Bl!+~tKUAKpFI+qqL^x#r5sO@*a_+g11G+}?3jZpE*}lJiUEyqtPs z&++DU_pg3b%nF`oIw$gG*4Gtr{XFZQ-JG3e>=n9grPk6=w+HD;U)-XP`Cgs-qUY0j z$sMP^zPYu-yiB6zwlz$edW=f zabeRsi=d)eQ-gfp^zPKkjor6!O>F#^^)K(cR<4tof7T-En%i<4>q@PNX@$$Po1Uz5 z(meGowe#chywDht#UYb#P04-6E?8#IWwyuGdaHgx^q)m}vVpU=8pq#?kGiC`?A5VX zo3!~nPJCPwbYS@+SK(uiyfn9X1o6xXoYiU8Tkml8>6xpIbG3_hyqjwCqxvr{kAT3x0bk2XQMNyr^mwd-|r_i61pfOwaBO-rV)P@$^Z*UwKBm zuASKbNbm06^~YAs@|ibP64(q z$+x##SFDKkbSzV1JALNV`TaU;Zco}hQTNm2)%EO~w^yF*-!z37|Km}^EdS@=A*pt| zZp*eN&xmg?-&|<&RGFVLT}`JsciY4p@2&HMt}WHMwpCDay352zxBeORES@ZPxBsTw zfs*=|xo@g}$v%&JH(lX=M5MOLME=7ugrv`TyIu_4+@? zNt~yb%+E~wYbI~3e2V|GKuT3{GtH;O zSl@6aSKaER|JMAjQ2p$&`#;0BPv^~>UovOwU9_q&rXQs*RjB-Z*K7If z;cfX;o;5l9{p?*f{=IvDoyUj$my`c96joio_d~wn@vJ33)@r&xsy&l3Yf7eH@cQ4H z#>OgNuULIKzp%19BU>-r=$rPh*Zcj-KWF&G_uALzh1nY<{jJwI6yE=9>UZ~-g15qx z|3;V|{8lwTef{p(g-_r7-1U=9_NvQ%soS-CkJM$Qo&3)*{mIgQOy_#z`5$}#wzg~T zNj%`nz7WYyJfRMgVmzDtyX+KkQd$?Ca3I!B;vp1I6>7_Qv|JoSwXSX6CQhJtph?&+Yg9$FI0%&(kYB z?^330*tj~~E2zYC*V!`5{|s!ef|EY4R%*S`hcEF!y}@6SaVhO&Kc7tb)6>&_2QE6= zbGiTIWYzCNmjxehxD@vK{GL}QHJm&^t3<>DPx<+}Y3?-NC@-FOdvjj+dM|^`96P;& zGBuX0x}BfonVb8@W9Qdd$`!G0UvAZ!Ze6=@c6aur*QU>6?aZt0``xbn6ICAdUhtOQ z((5ZrMK@eF)t>lhdw*Tvhth|T%>FD$uQ|wOaU;30|t+=rFM(|lTuiw9;Ui6lQDt<9@zrJv7rvIE1?XhoU z?qqySiQJuMC+QRz&#ScF=h?pSYwNyCnJsOcUbXMqIoH{n?%aFf=U z&dQ?Gw!C%vQt{@-UWii{HH7>*@P!uHV9m_WU7|q-sDKhO-V)}ZjXL_3vhpG zBolk!{DvLtZ|vo`w68>Eo9^$6H84HW!$m7<0|uF{f_Y`p>!NQfS28ds@=lwtSsxaDT&wsaA8B>u?%AJevA5|6B5g zM^mOv%6=pGQzSO1r1Hn+Ge_snE-c#ZwPb2^cHYzN*Nox4!js-|=j?>b{u__f`RzOWcHy(Qbf?^KsmkR--nrN2 zxM?i0xZM#b72=kXQtOe-J>9Z()f%2_^Zy*Z|7`M^UpFt#E-ILBUUVc~+-=^sE}) zEZ*F;Dz!l=VW)5Wl(_uvlcIHT)|M$Tu{I`|IXAxj==tn@t)#bD_UVhA5jSUSoZURP zd&6#4lgWie+ci~HmWn;P8#Jk8v8#=hH>~Irzs~>Yx!N{S@9-P^vgO+f-4$1y-&oxK zO1E)x$ettDgtes4MqGOQIdR989jDc$0(DLvk<_&H_|-kPS2*(2wsnslrN(TzbXz)j z((SFef!SGi5~}ux>`FcTv*^I}o&Amn&-E;syQkM#=F2-dJGa^=e4DrA=B+DpCdw)C+aK7p#(cY0O!?EJJ^dx2p?6mbCApSfyAXFWciU1y zNtH9ZiniQ!XV1&CeXX_S^Fxi?>Y&e)-uO=Ui+^6{{p{G|Q@8d_UwP8?S8+D*3jhY*yX2D6mvnS=?JnZtG;e&(i!_Pq*Ke4p-UsHaaBg z*0vdZE3f8Fh0j(+CL%x|Q-Jn!Ga&aBL%J5E~M*(<3osV=s1AlzT&VodFyZH__r<4v-J7^FZgq^s&KW<2)&xzNsv9$V zP12QFZ+y)+PMZ=S32G#=9zL*@@5Zf3qQVi`yq%j=mwxV9u|rIB(la4pmBp3@k5~3o zZ900WW|7f$Us>zeTfVW@Drs6DOaA)k1fBs<+$m)f~R_+O*_v@ zEt|XScHtv`ZMAb&g{ykA%L+msWtrX$oA&8tM8V-1r3OnQqdq!M+B)^Z%$Ux}Z%o22 zwsWbJonGC4YO1*uPRw4CEbGq|G`V_G*S_2N2PSUZzW3IeP0J4aj0`S}w_0^=d*TCuHQ73g?AZ>n zocL0y9i-&js<~~+(Mg9pP1bH*s^!(aaH7O?D}T=0N6&39w7ApzLT%P!|F&qU>Y2~g zf3`nOPA}%ydX^Zoy;|tq?8zE_LcuMM-rdRyoD$u$=-QRbnQM=jd_Q4-s=Vtz!+~qL zzl{=tOXL4DT&|v3b4h>x)7SqQ-ml-M^EWST|05AwFGzXBDeQbiF7l~qVZLFXev!KJ zk~vfLM33@Kz8UtlSYWD@$ck;EKhAVa5}G=3;w-Tgw^-I&*BaN|YucI4tygyMxwN@d zo!eq3t8=f}Jxk84Ow5Y8czVar4JA`|@0upN^Y~q-@F~+*b*oNzEUBlQvMOh@+@52) z7fuI>hfcV1e(5G>{)sEwudzy;5L`UU13e?yqwt zXJ_tOH+{|H>hB&WkDoeRl)Yzjk$7V0-a^Myrm?HGy0ckzeSW)k&8D3jT7&)Lt*%`= zQ7SLJZh3_TSzIj|?_*6mEyJrlm(F?z;(MB-YhP2iKI zOLMMuz92 zr*HF@YN0P1H|<m}Cpy8=uwgc+#A0CZkm6G-TCSbt zd}F`lPv%L+yhluRZ}A#$Tqo7_dZp>MJMG#VcWNXa7np1^si)_}t_iztT_`<$a<@ff zv3EW1?EV{_a?eZimqx`b=JlVsvs$h=v}^Z@hqrTe-)i0q^*&Owvs`Sa<@#6i_MWeJ zzW2i{k2l_vm7eQev1XQDm1M$kdZv~0HF1kC8oh>G%3h^cSEfw6?YYv;Q`zw7U*X83 z&ATUePR+aJdo668YhZ45Owf&On|FJ@n(tHkXUBt#nBxncJ@*Ri$-QyKP(P+o{zk`GA2zdQtWL`%la#)^4zT|HeC(x8r60i!1wn%|CPCWA)|l z-<~FaX4Aj^>5NLne}=!Gr2Mzm$JCuI>p1*t<fQsyWt>pK5@w!UfFlXY_Boq0@diLGnfDyVtAnloy`X_wlNb?q0rAIzUn z7PZy-y6Ebx%{hy&7=J7|WBEMoth3ym=-kWKLf_VQoLV>Qa;Vg1*BIRJ@w= zXx7@3rXhDjgV#^^yno%F)n_H|23`5eqib&WjrD$YL2R4t%iHt$R69<$6*l+mNEH`% zd#Q5y)=YK#>-rt?ZzGT0|MU5p{I|Kw?^sm{AF2Du~(CWkMHlTJ>Kc| zc4zeD=v7lLm8{Czy-h;*WB#dY#}&7%Q#m~0j?dia;!M*;go=4A=U)*E;a%!=Fb!EqLvBH9yP$=%#(4 z50#cnYML(d-?R0b;kJif^IKDxgUBH@0)!`K)kx_hHE}8?XB!i;qe!dwlrtw$(|ybJlnL305gk= zJC@0(=A4SIy!1|K-=F9?T{$h+buLX&^XcSH`6YB^qQN1lyqSCw+GfdrW?X-?eezd3d%Ts)m8X?{{l#m|G&$6IqG|rA?mOSt zT~3IOR$d!kcQ`2U#dV8soSFW{n{FTReit8K_E3}i%~~zb67QHx3-!cH!;eauzFx5< zR;Oh7{7<^t*S@@5Vf8zEo1$*t>}2bWlkRR>8JZg{l``#e%*@BvRT6x|=G>~tJL*&Q zP4)Jr@H1xX7lcJ@I=SWRJ=KCEGF{u0pK8nuJk@c_^WAN&7q7%xu0@2(Pr9Zo>PWr_D#H$_E>z+suS8W#*wPVtJDqc z^egYK-8$Q=vPo4?qvujutQ%+2myhocE8V}SsH*zZZ^dofc%P%!wyc?}Y#Pq5b>yvY z&bRGKk6uQty7=bip4(2dPD|gBI<@bt?sjXDn-RhKyCuDxPgc#=UaIfa^q(Pj-@zR- zHiw*E8O6Qm#HA$@Cr+5V_T0HTsd>M}*Z$Gey|;PclVfpqOHaj|ESB7!A9lGo%D*@~ zC+4Q=pA{?B=Djlc?dxTqnIzqYf_@Ee>clop+wb}3bF(m5B4Z$8`7YL#v~ z=kD~GKV{10l}4W=T1 z7P_C74AVc>ClNSPbIN<}*nmj3)2Ftb?a8~tTOat=RJhM^QJ0i>9WwP zwoQ$S0XZ>&Gbbe zE0?|67`9~I(=ySiGb>}$b1UBIGp>IqsnK!b*TjjEl9fIEr`+Opa!mOWn0srx`~u#Y zJ5<*TuGw+C`B!o7;SHx}Oy8(?w~+U^-b_iezrmZt)}5TVx!bL2s^;NW^A7&(=;%s$ z+T&mO;@Xa>>P6e-KW(pHe5i8v^}OVo(^t;?jG8=6PiApver{cJXwf|N%DY+fp+rkyX^rr*;0@Fm~$a;GP2bfp4%M7y=Sx_a-<&5O-H^yA6Z_$2R5 zZB-SjM`H9juJ71(^K6DTP-aqPH@RvX(i3*>kKeFq%NptJhAFEhPae9XGI?pT_NJz3cjskS&u_fkc72}m z9l2Y^5$lfplx^H@74mppr?|fM*@7=Cm)-U=I}(x;RTJ_=rny!l<>D>x_wpq-R~|B% zes*rO@4lCLU$Uce*VJVy?+<)=-8wU%+~~`jzPIk3yKBrRMVD<)3tp-j9(y`_(JjaM zV!L}C8y-u=zVgu*wr$_Gx$@kRQyPhfJ2MkI0|SM(eU_UV=Ci$2@-|P``c1E&eN@%@ z#{Op4G~II&GqT0Ra}V7qT59!C)$P%=TU+;MN5Arq>#aF>=F;^~1;$33-n|Gbydv6p zE@(UV<{dkvdR`y&IP~m9!S(H>9IXfMOqV(Q({+uJ&9;A&#g4BIo~?E4yYkjXPv5wi zRa76t8nd6b>5CC(?c~nKI%p7&N{X|>FrN9vFWlgvpmD@Oj=!S zw%uQIV{Ospt-R4ocP(R^p7hPH^n7)}?X8?KJN0CC3r*)ym@}Uz>B)<2u4doV(l^GO zd?X~YM1N{bp4X(bsjAVZBbIsw`7d1RQt2ouDOu^^UEgD1zkKtjkNABx@#UtA;`zrcbY!wP}OLMVXJh#WPa1mv8g#oID}9wCvM` zTPnVN(__6|Qf_LT{uN}e@5mh6)OuWWr`(R0KZQ>pZgV}Qc``iFwxaaNX+5u}#%j?g zr+cpX82)&gf3d)Xnb(SARrVY+b=yASvB!pq@}J!A2g$g&`Dz7)Y?ty{zi0nK|A>80 z3wJy``15Rj{*&a#ANC!6>f(J(Z_VS9+up4`7Ki>b7^k{^H+vc<;hIyvZmHX&c_)so zyfZZ@)6F-1X@^bKqrAO!2cK{KH}k-2fzxH;*ULn&c+A(Bc%vt5`nB`Rjw|n(enai1 z_j!*Qzjo_x^RheTvNBoT|5D+PT(`TkwX1Keewv>%f9kf%{|u|LV;*18jo*7k@9C?k zCAS|oJ(|^Y@9OTt7_kN0w{jfg&aPe4{Mjb&kM4t%cS%B(@owR~(TjSI&${+|S(r`d zIZqeATbns{-F@S-TDr3CT>0`U;SA-dyIw*|MYqLVKmBp{ojo%?UYmEVWc{v(5103C z`8f0FdiQ%9?j4Nk-7EHf@7#SRbN_@E9-I1$d*8Z`rn_WhaxZFZyQJ2geoQYXi|y(0 z@{l5HpQoCIJZIbfiWT;*OJ=#b=eDuZPR)`^UF&0$3N=HrrCw^jSoiWtiFWI4tGMm$ z2g}Tg3YI^aQvV?PsN4L;8F_4r&(GC)<~p&lJn+KL#j@|aclMX6?X%2zyxruxr`O#R zwwJnZFN^Q$Z$J4VJNNRLLvdTSUW^V{tN!}sHLs$SKXKPK241ghxO;PQ%FO(*?WQY5 zFUh{yYs7U{>wR3e>ecMy)4|Ey%7>`JK5>_igr}N4LM#UrfHF zWSjrAdq>o#t?BbijZRBO#WwK@5ROnY}vDSX&^yP!{}jW0KYfq{YT zYaP#q#9I{^nNqho-g-ACy?fehrF`@*)-$f(IV0lfq~HlkszRUIS1fj)X?HK#)9U*iGo{&{ z&iyBrJ$>hVsYKJ?+SKWr)08Y z<(S&}F3;dihCIS~Z=_6xrfiwMIOPZ5zLSM3+uWL8`J9fNSYrH1vh;-B=Xm{_^I1M6 zJ^d4L&ii^#)Q=R)<-hDp+k=lqM%+^UaVu}i?sBOkf1}IaNAK9PPP6LF9IM3@h3}{Q z2%URx!Hr*+%OA4+o-&hv=j>TA+jhxpRo@e8<{vcmMA@d`H)XLtnP*J4M+C`wh1|Nd zcz49fgI98PCZD_eT6(V3q)(0nG#@^Y>UUh2TF7A1AZuXS7r;{_S z)@hhc_6=LPIA*_uK35KRbfS38`p0he;`|Gb$0q*WyK3c);EFd=yE!)|7|%}Fy75+! zMsnEMDU-IW%iqkpqf689vZUcX=L`QC>^_;9nb~aJE5>z4aogLaZ!>rIaX-%dc=6ko zTPxk>FS*yTZ<^Gzoogdr#s_~&oj!H)vj^#SO82}|dHOzQ<;iRBE2f{*x4K(yS6+DM zy`py2*^Z1acXmjvj!4_RJUMaK@|erE^|RBbZ`k;zYVEw0>S;ca-=3VVU${s#wIsz- zEoSPMER(P~rH54%-9jTQBCYyj_sDpBOu0L$C%pIDekSki+D@}G)w6ysc6qWc=Ipb5 z$$K__@!=NQU6#Me@UF!AzBx~>=pM|S^HjoZoyR@(<~q?m6%XIC%D$au>ABT>m-0rn z_gSf;IX6X3H*VWhX18s{CYO#Ir8Q2q#{zYys9lTB3a|UmP_|^lwUeK&tGu*gU2$iM za((c!+nYic|7V!(e@pMwdds!%W{bD)E%>tRm3l<+`v*5p?qB*zd2-l?9N)uxwuidU zEE1XKb5~@J%H8fF@7>#$%y}A-w_Z%2@19-Cq(a_>r*ioC{F$=9ZksZrw=QwZ^2-rN zy+YMkUt4XKI?LCWQXHO|d*)r(bdC$Dx4kBX&)JuBu^|2KlN84ozh14g%O5`oTE5CH zX79zm6~$p%XD^oI%zSuCD{$`dd^zLpI}dd_Zw31w=F^>BF7Dr7^Z4O;!^M89FWf#I z|5j7)>WR9`JVDEXo~~M2TBP7pbviC>>z2sO(Dl26WP`6hIbXI#YH#xj(~!hhyW&=g z%Xn|t9lFhWu6b_Hq>@jI&Y#)Sc)q5!N_=Ox|EYt8J9g;3nYz!^Q;+-GQtdqNoPK)IoR~|_BHJRMrU0c|9ENwmSt%;KAsW)6!uSl7_{+RJ( z_soJhUDFc%OLlkiF3ejNZNY9)@>|&M$mTEBghL9^Gqf2`Z}B<*R@m73o{S$t>D z+Re#57E?HVcC^3A)-A977W(eGymrNf$9ZO(R2Lr6>6tQF$;jwM5+i5;Kgi=7-`&&Y zYma@MsTnoN&&b#0a;UHA#%0@L{qgS7Jr*f}~+rDFS*Rp#_Q!l$j?1cIxGGkD}VQoxMe2o3>i#ZO@D8emHlH z<-6^9LTgedN}YQ$HOEWry6l^$d2)Uq<=ZTt%EsNj^=rCIkZ8{GjXq`}zgBs3mJ0A* zJzXq#{Y%cy>eP7AcPj#~A1K_i?@{!>8;^um`YW6JT*`I3`+M4&DDSgi*B3e zlEF6dw9MBlI=^pLg{duE_1r6a-(JtVpP%mDx8Lm1l1ulCGB29%G+V^W-e=z0=9$km zY47r*48s=rBIaDwUm~bYdt-0FFZSE zZ&8-u$e9KYVY3OV>hGvjSrdW=}y>onI~RmI>@U+llFBfrd54ogj@}z6)nL@3; zN54<3v)HndBXC9QyQ6PvI;tN_11I)CS0 z$=g$8R)!xmS$SmAt?6?nm1Zgj@2_e8v%F&cAKtY4o7GsHuh#!(@O_v1+2c$5pS|^e zzkWYDf9;v;f1KJ^IxsLWNH_kzyZ_0?U3ND(?%fIgM%Y&Mz-&QZK z-~Hi_vCf35{KNe&g_kGpS@LYz=h=;F+YYCu+~$uscV?~jf`nb|e7ie^RZkg9zl;=L za53Zw=V{%X8HwMot@s*xF{*I7(Bo6@)>p{PUz$1VSls^U2kyK(Rq!ag?b7tQnz=WY z9(n5a%}q0CYD(zYf0O1qJfAnK=)3#Q$|twC_cv#YYwzgU{paPaymPj#cdr#~S##*} zoMg}V-I|3fHI8lVo@Z7kuAA!GwtCu(oww&)TOe}mL+-Y3+V}UgShVw7r^m17&~TYL zJk@8_Os zeW$dn^YRP6@3rwP)%(=8Ep-l=FM2LG`Ofm^*Up?Uz2@mWGpj~g@PGjizwU9J8QWK$ z(0-WwCS}^^h37i9Cu=TA{pmYfWy*>(or~TIg=Kjq$}M`9{QR}Q(vc|-CENrvubNC3 z{?FhQWoj{yZ&B?`+Xy*M7}R>qvulRlK@`Gbdk9OMrG5nyphot=Jb`Kb+OCrWT)Cax;<5>%}jOI zQO~6kJ2g&yJ*8e$JnfS4Q#tcGu^p|4zm(nCbbRUAzP6acKk;#SF^}eYR}lt| zZpq5BbgBBAOIN0@+*&E(Qs;QJeAdspD?d~xi~VQlmONSYu{)k;{lPCauf-PjZOX}* zzBqT=%Cc{!Gv9o;y|?bpr4Qy(cBdwr$LG$|`|KPOXSsf}uJ?(#nTB~6Q$q_<1JbU% zvH2wHdHLy%Cr7->R)x%Xl4mYglNM9js(MA$cdfSd6upapPcR_uRS@+hRS+-dzhfTq!(BI>`_#|`DM_oZ8=}gIW{r5EW zkLDW0+ZUzo74iV_w#GFcRX5}_hf0aqNMb#e9s4yzwa`6{<=ME(0>Ns{=Y|G$5%h7WA4l9f3*4g`Jes2US7YS#9O75x43TNy-&xV zDOD-H2>dN_x%TM$Lz_4%n;%N1ojm<<4?D}k4X2D{_C4ZdGw;7@;_tt8+r~;A*RDe$ zOH6KRX1&@j6C{ih@ycji~~uD))1R(V=U z&Af!`n{*$&xKWl`_Vwm2H-V-#v+vz+bXC_hTP*VAQuUXD?2cW}4}b4H+V8z-dAed~ z(C4$SH?5KDXA^n7>eX_!XuGd-<~=kh*rUZ1m&R$8;T$;ONjt~9+OOIfKa;0_tW5Td zc)9NHI_t|HPR%a+=4QS4(dz1G8_w7JBTC=x{>oK#<#CafkJ`sCZ$iV~-Yi~Hb2U!; z`(5?YRol%L${PuI`{xVu3+pVOfBTWE-2RYL`wX&XYks};jAij;$uq}}s;vl==;=VS9iP%s-AmNpZl)VyZ*+V za#MaOd};gc^QC%bWth(CnV%nuPp@678MXcG!p^MNiQS*q7wn4JH90SEd*+J%CHuCy z?=YQY?&7UEYwDZFQoS2zt=L>N@1#V@>WGW)mmZyWCNh893IW;ZXKk0-<-F5w$)tL# z2R3Ey`}8L_8u|x zwmxwpbH^G>W4qIDCQo{L>0Vw`wD+d;(-S`K@qDXs&86DEyKzB=nDDVSJJVIw>4#>& zxov5uX)irF=g+_FKX`bV#FKrLp@X7Ft_r+~XuIk-Awoz-9 zeP-3yotN`Y7JS&d(rUS<{BeB&J;_hkva~FIp0E*4NXc`Yp3!5ycgs=Zt=A4rRg-!7 z;=tytGxE4Ypb{=KMK}66)&wQTN#+Mo@dhZLm%v} zB+a>c*{|lwjNN7Bm$4Q;YAE$ogzLr_< zn{Ko2@{V3V$t_!_up7Od%q`P2>sD`bW4Q47RI$si63ay=ZrYP3y?RU0N!_I{3l{CW z(p*~Cx2=G$?#UaQJ<_wz&CN6W{8mqM#VYLyaW}VYD?D0xCuFtf^4Q)y`Gx-(mh3kw z`fiq3a(vFG_i06Kiwh%;PQ6)p=kUqdUS|@ELT4Y}t+KXUFUIV-#hvX3rk=QSMW&|* zG>kkw$moYkU9te@8==fqYmdK>nLV&`r*%uUq9(PddPiOHbP{q~s_A9d=@k^~>lHZP zN$GRr;VjKleiPjSES|2AKJX&?q;BAKwG)b4w$CbE@@>_l6<4-BGF!U+cJ5`b{VKuf zH;dQDUAlbZ&9f>urej<0`@gspx9Nk_;VXIh)Ar8$a>xBY!)e23mok4>mYux#Hjd@n zdZyAfGsPzvG2jK7I`cV>8}qw<`^UemN1aI`l!wO6S?j z9;JWy{mLfBy36~u^1Av9^?xorc=P?4d&`3V47@MxFa9y|DU5Eov0Qmuj8A5l`NJfW zZ%QjgRWeJeG!wDmLAtTgSH-8{Xq zLcA|}`l|10ZgWGP$wj|BuInGPGtcp(?3^r|L(1!+Py*ha)g8CL~cBG z>!y|Y;mfG9r6!&WFO`|O^UhA)3titX zm)uw^y2&sk?wVk9kg0v=v^Tk5w``Vk%bl&h?5X9J=kq#+Ke-%OzQSwwu2ohQ?{u>| z-Q5{A`bGCGTlZFW>B>CK>=4)0uf&b>PnFB2++X{^`$)fARpH4WHJg`49Y560X1XKD zPICL(b)Mf&(^vK*5P$#Y*l(O^H>RcgSmZ*4@`do2#_KmWxZeF1hR*xNP3BSvM};FC{G1ZyKiMp1`kj}zic((s?bQoj=HhxQs^^!$+Gz{xva|DQ*S^ia znx@}>@|T~@j$9{$V{zB+2=yrK;WG48eH6_#Pj2hM2a7kHvyNNz!u9I2&Z{N*=2PS1 z7R`MUSN-Jl(Y^A%TUJU3e)Z%&I%(ZCnF*J7?$Z}Exh7cLrImSlUGT)o*77ISjh!ci zdFQTMIZ-Y(w0VhVRG4SMgiU&vgi?zy7QcU-Ri?J&N9>8CKUH4*6E?_OUeq^vU6J0( z-l8j^Hxgra=Pp?-J#E3e=z3j|dGAfPMj0ob7l=6P`LJA>_mc7#-QwhelQT=-_$;2z z^^Cjqsa{uB4%-#?4?HcK*SPw7@0ZMbcmL3>%KMjdx@X+^xioIeOVio8Rg-G;ADU(J z`fEJ98txr_+M`|eYK^JpmRbED&NzM!nH*<)HK+3XG{xe>B4;~njLO<~%ZgjG&eq1W7xSxtS#63yVr#kC)o*<@PX*{D^o z7M=Xz)$|FUzh6q8`kz6#_)h6=gNKK&>S$io())E)_ih~5hws7DThDGwetzV8WOdt< zsbTee=EdH-HnOF9mGTHlh2=IblI7i>d8E!Q&+}MH$ePYrv8{@Szs2$|ZJt}Od(VzE z*UIh6?oa#9C(YZa|4Z+){r<{o?P=>zZQroI@SypB2KBN{i{iAqO5c2*_E_#QhuM;w z`aL(k#!Fb_^lVql+@TkqD5>}QoY+e>qszAzmn)eq(_S>?_SYL{OCnakzH-t>e)0p( zeutxd(-%2~i>f_%=Y2Kw_LDB}D>mm}z1v$pziRo!Y(Bq=4=38hY|U0h&o8@E6TDY@ za-3$>Qs*Oc8)B9}oVM{)8RO9tDsSed8%x~Cco%fp>>97qY3?(EN}9_zT)erT^Q*4x zyn~bX2Dvr+NB6Nloxj|vTvN}&RA2hGWKA&Nv$sa?HHw${sfmj&@0~0?Ew=2Ke%sq+ z^E)jrzQ6ga`VjgRQpAH=63!1_w{S#-|qA$_6DyH z9-Eezx+dcNPLn;Id)HqHP0crby=`auw9P-8zFHI-=EZovo3dE^p#0PGZQ94q{AZ}> z%6)OC;$hs4ZA+rE*S`JOX6nW%+d4@u?Y>#g#){Jo%R|zNG&JT-P3YUZ_{|}EiM@~!5cSmmYeKF7Bnd*!KqagyEfj9oSB4+tOas=g;9y+hPCw|!F8a-nNQ zwO`}xYhxaNxZeA*CfGA3-Oq2`<`Ylfo~l+i3w*LY_KTWqU z*O#x%E4^`g z&S*ZdnDOhDiRX<=GxzMR>K3lL=E`Phbt&bRmHWE8iNDh`v*%Y9YTel~QF6P=$|(x> zqccsv^{QUpS9$$i79Xi(6isANsm?g{*$) z`{C-Y@Ko+ekAz-lXQp1|Q}l?eS{dc5rGHpiqHZ@vY5D!>*G^4yKfTLm_T=qb_j5k{I`O*w3;TZ=1-3sYKD77`sa%bd z^7-cT_ptY06)1NLZFg~daN@zrTP8P6mOp*{rs$Ye(2R{UC%FX_Racx2JX0~rf5YKc zk=^oNo63To?p1rHXLaW6@1m@g_C4KizWZ-KxchJ{+w1zkf}hRd^Yx1M?5NBai!q)P zf7?9i+o{#HGi;|_duj46*H6YE`sn&u#ozM6zH^n#J-pmMGU533`)aEzes@c2bh)&Z zUa8Dkz2Rx8_TA}gf2+TFZg$7n7PF6?k?fV@ksXOiU$F=7lFSx>d{N>FB%hs<*bldjec<#JgdtL_x zxh$1mmt-_&$FnEDeP0|o-lu0{_;{CA+x0zR*9x|>HC(UQUvS2n_3=fcn1z?Wu|0F2 z;FfpmVNq;U{~inNnng>~Qew}Ph`US_6uG(Zp*yqp=A>7yvs-uRom1Iy>!;@F?7U^m=JedQOt<>}EqjwuP1D7*{{8Vc zFJ|?`8l|W$S}xeGelgLrnemE2$G4d(6}@LS*5-bHx9_1@*1MUhyvJv~+gPlwkljJ@=@l z?C8@KRzEqbIaJ`0y24JMyvwom3fh_~=ba*jCdJOF6X&tZ){V{o>Ah;{cW9VZrR)uPG48r{^1iY*WBqRSS{+? zvZu0OMWlSY-=WiU8}6L8^_}~w=h@u%?&eFKW2cq-u3J05Z;RmCDWZJk8u#p{s0A$! zpJ?k9{``+c>Zh~+R3mQ}%qh$D?{wYz%JWzIym=imrzfADTXyN{?7n+;xodi=S8WMy z-fVrbXOF$jR(8=(p23+*fwW!ArQj{gcs^N0_$SJ|8pBtV}a8|Fu_U zUz}l9Shpzq^48MKZBKsB&a_nhc)2>LPGgnn&G7A6A@lZf?{e;6k@@L)&1=tBlI7~$ z!jpV&9p83$_vG!{Q`W}>x6BpUp}SQ4u%%{#*M9Ef3yh{+jyruyWuoTsOMaT}AN)_h z>PUN5=6gI`ZBP612%F^#6K;h)P7*G;^<>pe?H|P_)|_sWel2)6>RieV9>2mR#YLC-vY`LE5t^QON);Kxo?p)v7Q#R%=bx&5*pL^EI{J>M!GVR;9ANBn* zy)ij6y8WGRW@2!{Y4aa^mk&NUFynBC-lOM2XEUZ9J`p7A)G>R0U`m~<$}gckOUx#& zww!TJZ_@GI{DH^3A1$lk&3d-{bkvi)TSXh!y!`38Y_4kF*Y2slraKQ6zIp2&)fV4< z=D_Ek?{SefE5)i_Tc0WYJNs46tmw-h&U77@o%ZRL*Bx)K(qB(CE_nqC&i7mXT&j1~ z!@c%_d)vN!FISb1+)~$PRexsVb3UKTmeVv{pFND+bE$NBWuEcU>e`JP*YCZ5!9cHA z_xefe6~1=w#M#z|KUlwO-sg*JKZVWNbvAvI`$fZN&P%#uCHcfNy^>CT`7LDp*2`A? z(&M6kej5uzZ+qRb+4OnYpOppcRc0>u&+tt4{PhoC<_a&n5nR}p@_g=;Y3H=d;!AtQ zANH)XzMP%(Xlw4^}Sf%!z zTea_K`{UDV=FYtvxyW$e@tnWwnk}c6@qW8v99MPi`O*jOQb*?G-CI35`ij_>`N@+r zD@FFWt*cW1vR-(;<+g9zSKVs;!)tjvZ0+fs^vQ+WCB1Y_H6mM5)y4ho!scC-RQAiV zt@VsJTsKu#I91ByZsDW1DpN0?^7Nm*P~!TUUa!A5?br|V3%)tme^SbBZkpYXZR?&~ z&YpHPUP${-=xZKst>c^5uw6UPes-g_$MzY=!n@B@E&9}VPw4lF%Mbjzwpd+WyZS`x zl}lSBm%3yIPW;%tDn0RjpPTJTw>PoD>#khyxP&7w+s$l!^J42+b17lTe|mv8w@nq2_CKK^ zXvATZbZeu`)vIml3*|3;wVnU8HqYkZ;;1WT+P{+v^-dnIpJ+SD`@qEq+vBYw%NOtU zJOAX$b6eel`$y)iy7MtV^!Dl{6-#qh-kdXO@y_Q&7O&((1DOq_n}IRZC5L^VN#5PG zGF?%-GdQVe)%5i$_g6b#{mld>egELu&{LY+9^iYbayzz`<+S}-rQ%=n;G*Y<# zgq>-hlWl**r|TD8Qk?FdjPfb%OWxRD8ZO7bXV!;f(wR@D>`40KJ9Gb1`GEIdt}oiP zZ*!w%^tGY|8#XT4e(YOl!gKLZA!?l#}{JY+4oGrS{RH}OMz$M5j^MJ(1a{j%$t zo!75On>oe0=RbqyoTGBOQqjIU4zCXjKRu;**|u939xXUrF(GALTeRZZEW=ABw`Xqg zD-7xSaPm#u(z5+R=^eF(Pgd`Be%>P&-m{cj{GDaotozbtm**bazD`~AFxQ*MQ?fU_ zUU5=q^V`?6o=EpsJrlppo$zd%kzU@rPn=y7jeZI^FPBGb&tzm_T@Z1`QUcg-MjYcU32%CFZ7?45{$Z6 zCJ???#;E?&*Z&OPuiqD|+io2HkxLGIT2UVR`?CKGla9UkouFRZw?WbF%luDj?)Uv) z7ueMQXE6BBaBE%fzxRhfoY^cF_fvVo&Tsy^zC7n$sk>Hl>)Nn=%{w=r2vqqluvura z;)zdOI+?T6r2Q+k)TEh3Du1j9`aRWibJUfaYum!+&0CT!Ubr-$Nq4oEOm5AT*;%F^ z-COsw#&?R#JIT#@@VacX<+eYHwoZ}$Qg>#p-kf!Jhu`b&J9D3x8Rkt5-0t$yQhhy- zjI~_Tp-9)9@+y^1Kbxk)Ec>Rk^j95z&+>dv7FwD^w}prn3p|$y?y09vllTNw(OC$JozxXcc1yTwy$c{t~vuU;s*(mU)# z$!qVvxu?t4Z<*%iU9R%SKSg|UtNPQNkK&j8=Wnt+zfSU2jGkANTPw$dn|(*7Jeu-p zsq4q{O2N&!+a700Px8!Wf9!tx&g2~UsYeD3?iZC%IYz6?vFkZ;`Q_Z%;KR`XH!wE{`}p0 zYfbiy4{fVETdUQ@o+Lky-k)@5opbwE?K!r0k8D5Wo4;_^_0Dgs(Hm{-Epr1?IaeNc zUa~p2JEC1GxX-wHwbr~FS0*Wyr%eyk-K`V5$vx%o^o&a>OICW?wm*xBNXw8c)6-ME zraCC|^7BgJFg^7@k1Sf_c-u7>5 z3o{E|F25+^-@EtMRVwmdZv4-%V{i8R8vX-?X9NF))&2a>VECUQ*6Q~g$Mr70SMo1e z-;4Nj#NWl1%RXsV{*Jvr!W=hDZ)$Ikzm_l1dRq3V!;{*L^DkT7+jM;Gj5jB*|N3{6 z^L4$F^2*oz+UFF%1o`}DP-_o(6lLu6)GO2SKZD;+?~i3x*G)OL&M&jK?*7j(x%ut# zlx2HEWoOGQ>2>&W^-Je}2B~b}m;UN0nniu{D{W3+yFYvS^!$Imp4HhBmsb6ho_=}$ zTtt`<|SSG3nKp_c+)lXBEyQ4{IR{?qwKLATax*7q?w!q6r;K7Y-90hk!LK~m z(&TcRh2fh5A3A^e?P;`B#3M&#!sXa~hcom#l@!aW&ZR7vdeW<0Xt~Jj$;*>guX}gM zy4~kz(y@?riOIK)9iB30_iVF=S@%V@or*CC44r&i-D~@vFRq6_p6FEB9xfZpy*xbl zp8Qe!CXMH_r`mo_^Xxx$i_d83p6vWntKUrhwsp_Um1Sn!TMa_1U1w(RSv6Vz*jojQ zH|`Nz7SB8KP2knzwOe=d1#ZeYk#T;*XFlDn6PLQV2R5%xPe_eEz0v&M?OBsj{WJ44-6}irx2x&TV~saD zCY=fmoss!xr;+lClTTLdS2*;B^IFKd-pMO1E}d1Y<=zsv;CA#n^AAt%Z<)LNwZ;6o zzLxQqDyJQ4&Q_PU5~*IPT^@E{>`LjJN1v{nwQX~5zZJDuVM_XQW6zRT^*mW@F`|1q zkLIoX<@xl^%+wb*zCU|yo*RC}@}pl|@44FdYkpkX;XdPNYHj@5Cw_%e(dkcuWUAS& z8P77(+v~e^qo%FsGgG1N2NE~(rk*o;x_e^u&?PrphcB< zr!63oEKSFDoZkA0UHGNR*~c4yA9C)$=i$ul6yjG>v1n7iN8zR@D~*obDQ@mJjf`HG zZeE-oTb+DhPfG2|Qsb#6KmNS>d}629(oFN%+ZJ<$N~*OtZ`%E`BJ)S0sayJlw2P7Y z2`ODw$5ZA9z4>+_&$mqfbg7YQS#5tx|EAh?o$I%szVx4A>W;mxe@qPioZEaee)5MU z_y0V(^eLxn`r6p7_f~FO*MHc?k;PqI@5KB-{q;`@Ung(N&^x~&$and*v^4iCCsUu; ztNYD$b$a@zbl;QvYohc@r-#=&A35H&JbQLs?8_SEm-7$TNIunh{V8S^SCztS=X=-A zsmv@fU3vJ6$Et#>PhVc%;Hfvy`9*Ezbp6NnnU?D#|2VHY9`W^r$|l2&>+G)Wc>HAf zT&=3D>+T1wKVWgZIpJGR?1_ois+-!LSM!!-B+S6|E!=Lo_Uhf&V!ib%cRkzLt)pGoW@d6#^}y}PaeV4q%dX9x@*(u3V7W4A z6E!3@aFAMSPe+;x9QY^*{@ud&v~nR{eJPnWi}dtx1%q~hHpr1 zyS7zQRV{hHwA8oQAIT9h;mcl3KX_yN_8Z%#tu2naTv_aK;<{;d*R{F5x#x=NSU0Nd zPF1^-D7q~uOFP@F{Nk);)<^4RYnA6@D;j!7YHQs1d9>-)`fjTl4|T0~pK5lpHMM(( zi?*s+#e}EXxOHzy+gzA6%Ve$DvEHUNuB( zTc*1#zASn1Hy++;JYM@?IE-<8Yr*1F8u ze^Ie6Z?udp}U_pOZ04ZN!E@-)@GWt+?Q+PB}{ zE?xU(>3a8Xi`nOv-&^s!_NVR3oqG0nRQGkhk*Yp-KlqjBi`e{WKZQQ8ubwRWq`B_Q zBG<^eX;n3v?|Fs3p8fjXC^>KTNIx10@*^#;~-*`*D*eMMo z(`#azG~AQAyfda{H3W-pP+O*W+9&S)Ub9%W-?8fs?JDzIEj_9B{p7sgCm#jON%ea- zb+O;n$0cjcFaG4IkNL8+>R9*8CGO7emQL+1J2iiQ#SZJ+MeXAJdzFtKJ{l*dac#<@ z=#$=$#5EQbOrLaamZ!_!*h-O(YaZW%r^*TWZ%J9VY^sN{(!Uv}mrq_FIQ7-JS*iaS zmTr#wRVi*)e(BQhO}D?!ty5h6PRA|csiORZQ`_{Pc?#6tUu3WOY9IftvcHFn3?Clf z{P(~owe7Mq6>crPc{SLOQ#0J}skrX+Z8x4wEz!HV=gAANmizwdk;l?vZC0M#>1=*) z*QU+tpLbs@{91nW*Q+?Uun5Us1f8TyB~bFHuFAP6pWSRv#Ll|K z&bydibGG&US+%F)^YhQgE&I>Va(_)$y3@{?pY8{Iv_2UuZnop{r#qRqzg8a=eYj+< z4V&1G?W?^@^JU9hcAPx1bau|wb8fXw(Q(f-rLDWm?i$;j61LNccXjGtS-yV3F8R22 z@fSPndrYk()`s1ln))tz>#r}%r_O$QU;txR3D+*dPb zLfHu;CHu6Q-ZP4>>CJgCSH1Y`SM$>kbH4hry*RJ$ANSn4GF*6?ozVN}+uI)3m`$y* z@jY8nz5Pjvev8}*7aeZJ3ugWcmOl15eztJhl|^rN#wNbFdebFDeNnY;_^mnam*P%b zS$R(Ki^_zJpVAiJJNAv`{#?nz<0rm(R`vD&bXob*{A9oX-esTjOzKLjE*;%>?P(F` zxhwPg3X1zvMVH<>o%v|}N^i-VH^kno?a00Qrs`?J%;XoJjOHAd?0WJh$@{POX`}aI zKGV1tetEa@^a=N;R&iwqmaKgx5dG(7W!Q~hul8-6P^I%ZUAE_*-^uB7bALZN^-pJ3 z@0C4)^O-;0N#}o9z4q_ryxVvFg%lO-k9y266CTp4z5Army?=}LFY4W#vHzR2Sz6tR zXDC;c?k(u6pKP%|eq|Z8s(|1if`mbMb+OL3O`A6qGH=h2s)Th^5vry~(=A`NB zx8oP>wwpT;091N!!+a-S4-#$j!2CtHPw%j!z4& z9o$xMDJ{!vjdb9opiI+yKMxx>2^{ObGiUzPH~mMyp4fhI^R#Xc9M21@e-P%ve#uvr%HT_i?Z#rFDi>BMNgb9Cs2HvHCf_V zN!{xc3W?XZ1U^ju!IxaEYOwLnyp`*JZS}KG&H2xe#H#x@nB`8yy$Rc9&N&t3C01m$ z^rZRJpG{})WMuxF6u3WE22VxJhfDpmzSUNY-*88 z7T+$=y>v}jRODnIUzZ&x-bzinslBLM>556Lbx7!`ZF5iM%(|)iYVGSIulBeYp4{vA zJkDu8TjO+%hnD6#)!lnGs(s)0F7A?F*XC^x&Alvx6T5v6KFpkT!O-rpU+Txr6*-gU zr;D7FO6pg87jW}yFW&*0@NZddl<=V`u(@Oj7UPM7Cg-TP_V_3Av$n(jT{!i#TqSKpG}6Zh!2 z+=*8?8tb)E*2&IwUE{vshU1j`ez%Plop8=K*`%zjxAdv!#OXmshAnru79BmF^(7|n z=*49#u1}SAZ=Bz}D)IIeliS} zS64Po|FuPa#{A=+Wj~caJbjXJ^}I*j>NO_2Ij75jwm`udsdylMK0KP_CDSA}rP{7bewF>}h{u3(WD zrMA+SRp6@PlJ*-b0ry;pcm%r8dz!pCL1 z&;PvnzIW==`-N*S+zfdtmfgSlL+WGGbGM?czPy?C@#y(&^L4MDnD+S7l>9yU9+lpe z&+nF|Ef-sCs_B-Ne&vjdWu?&m!d-9HudA{>{mXQ%wveW)cAs`qNT6p=a{zS+mzGL5oIete8E3akuDY4<-B7bGi|ur983`>r7G>m_`H%>B`4sARaZ zomXYqwbsSwCMzo$+HaZluzE$sZ|=#hN2~OocX=-R_SSFf?WngOCM;?{oZVV+wz{;W zD%L<@+fq#)^~AU7XQr&z>bX00&c0t~{z>&jEI%zJd$Q2)XzIIn^)}NAtKMFX(e^rK zw)${pS@mt+KjwWmvr>MT{>by0tQ6B{@qLo2-JAA1zuv!}Qg*NQsMV|A9Je@~s=Yxg zp_G2G=mx#~uHm`U#&_+12Jh-OEsfcil`pxS-EnLCnUd`iOQt{itDof6T_;*D+P3Y( z$_H+X&Kvs9UT=&5vgr5eb-iVm!lmj~Wo-Veztlf&=YGf2Py3Rt@BO&&dF0g9 z-}XLT9{qT`?#IVlJGm#WZxim>!?U^0N>WsTDJ_U_)o{e``i z>*GS=RxbMDx361z>+^R~uRnK6t+&tnv;2j)*uHPuWB&+#<6lxU_W=V#e)fa4-?wj3 z|NC&e-I?1AIoJLEGq@PmY_HpEe5l@6|L=+H{|w#-{xke2Q(QT{>$LCRD}E`qH}yW> zwcF>P{a{kv7OU?kUvo@earQq$$9nfo8-I4p>-r@U-To@scelTN)2H>__bXY%B^Y-k zFy82X_%&Pab@Lv-xu$7Pnl4nGW8AS+YRa}%hE>02oQj%y)hqH?OW(!$Q_ke}Yn=En%(J0jCFCGEe? z*4uR5}^Ex@f_FzH%x$e#^IZwa8-=EfmFWp`%6g|nn`ti$LZN-mzX(HRh-sH&l zTiu$rb*jddJ0z z{R{M_r~l!tw{9&w9k^fZ<*l_hn|=fuN+~5@G+?;Re(%Xs@0qKATDM3q%YA(F-M&+k z&Q~q=&OTH4`d7QGn$K_E*(VPMxh>LL{YFs!)Py;|JnOsJjisOH9g5c%+y6+;zkbHC zNozjG>%87_d-C$95o^tV-}<_H%KBW}ue&$xGt&G0Bx=Wp^5cS^Sj;YZ@4r^<*W|s# zQ+-Zv?~@&Cd(7_Gy_u4`>0Q9j1-)5HrmGjKubj2ZYGbCwqq{%eNv@AP{=<9w&x$N@ z#q#(AtKU`M$qe4Kx%J8YY16K!Cw;Y(uio6}IP;|KXJ)GGSL);n@;EiX_dFizshCF_cP%_SC8`##JWE>pwJ&ztHZpb^ete=t^3y-!sw{_hhL=#RP|Kts*AJYk7ut4i4-en-ZJ%I3S4-`xkgxrQ2fq+| z#-={<9vF7_(Ekj7&38^etMvL`-2Q*Rem^>Y?OFCeMdvG^qrG_#{C#)-Q^Z~Q9G3SR zud%4#oBYq^#P64X^Vs|UGdz?39eQ#9-5+(5(~{JW|JnbaVTnomhx)7aF_rayzTQ%n zdbRwicfCjdS)V!fr$2uYw*EZ#Kf{{G5=DZRFB%xiybJqf+dDpP@87YusM?_Ltm>*4 zkHmumXNy*til|-Nx_Z%^!Xuk+1@+(5bW5uXmaboXv(Ii~caq#CiEoegdbrBZ&Rd!l z{W#AsvC7X+Qt6y$;pWC)C-<|yYOOgkr}V?=ro>QtNH8WsjAeq^3%Uw?Oz_box7lYPsz&G;b-^ZvS_l&W7W`> zS*vnGO|B>V?E9x+DO|CfwQ^$Jqgkp49-Syv^>E zWW0IYO zz45MkbienM)yp^9Xv-ddXgcRfm~fJF-p1W$N_Xmq8?q&b30}#u`kZ09^!%K29vfE{ z%Rk=pyDQ@2oRqZ%ztbg_>{xQ|)v6sAKkD{g+WPM4e6QZFlTr{|J zyk*KdwcW?|+^GNfLwM2K+ipGC7tb$=au3-#Dd?KfTEAm5i_M!QO(#y?wsmTzPsxjQ zr}%DStznn!d17^ zHODi@J29NQa25lb%Z~F8lh51>z8dz_!fkuD!1Zq*AA7I9G288EM4n+LUvkfs%r6`L zRQn<)T-_^s)Nba~tCJpSZkyuraL2L>d$zkhnU{F&!TQIMVINagt&6D<%zT#_c*=X* z#_o{l*DhbLubemKujI?T*t6PES5jx55leYoEX*eNV{cxdf0w!HQJ(7HATPbUw?FJy zsv1$S(#z7~NbNhf)3Z93&ns$XJ$5HHJMXNU^1Ah(Q{1;3CGTw(Jw0b~{HoL}Uca(b zT^*}+hpLYI=lqtO+Bfl;r+&?|+s(`FNf*t#&$MP?=;IrI;&@Z9?mv?~?ajGtr>8Y< zW@z`i-aoUo=kDGuQ>N~gj4`g(&b2%m`sd@t!f^Y)yvP4DyvzU3uzCA`hUbpKzZU*y z*i!$({?DW1{~4~d|7ZAd{y)R&{|x^OZfwt3xF*}}P1ub$nse$yQctAs`O~@8G_)f^ zb>GdeM!9Tl-0P>Ga7x%Ubx)*gUhnn~6Q3PpJ(>3Uo8I}`_s%re?oKZ>e=|2_hNon* z>D>A~eQ(FJXFe8xmAriF zmM1eNURS-h*Jkg>M{RNS@9M?MUh3X@IkT)fxz6ZkM({t)C(D0!J+Q9%e0tYXo7=a1 zZ-;*PZpoKhHTT!oGs-{Crsk*4)4bEwQ#;k=z{k6etp^JAX3yPrI`mPb$)!N~jj6sd zDVt`S2+pkLyZF=R#74bk@#UsBZ@+13iq}~B=HzU_7rIN``Ik!k?7p3H=frN^DT}`! zJ?(uu;#T+9%p1uqZpS|2J?IQE^qQXG+15#<+8oJ;-;dU>Rg;^$boMpjXl09r)^O(; z$J65EJ5Q+C%5hD2ShhIko}lW_y3{S#OAbyFo8B3;UhBoY568d1^QphCx3=?rOO0G) zejTgpLz`b;8eh%-u{+{F1NV#R*{3Va^SimVQ@4DUx@NOY{FfZlCI8q-{x@xW4+l;!*Iim# zthZ6Rup%t(=-XRA=RdEyC$xXzUbEdZ>+YLmh5h+iyH-;7^tRvr*S5Yo|3tE{CRnPu zE9mg@rdvU6(woRe=aRe*?PO|&7HrL!F~vc9=oUcdHS z%i~;4%lm4XVe3y-P5rWD?ddkocaN6F>{)5*&z$?-eIsD4%w#H zSsI03Pt|dY|1`Pr#%o5#TqEVPM>-C1o@6_`Y`LUY(9&m8+cswVc$qxfcQrFgWsm+N zz01*6)t6)bRqnKYmv?MxUW{IrP0*Z|aU5=X8JaJ0CJnvVQjG=q0KWzh|tE zKKIBh-}_6^(;2Sb%5s07T(zsM^1k}>eChVh2Oq7w^r-On%s6F9w_lec&gGxrIz2~U z>GrN$-TKS(qq8{pqLaS;oT^*|v8b+z%FSk=m- z7mr$*T)uPJEp3mV`rLe$o%{;DDS3}0{+4g>606nyye4d=OUlDZo@_Jsiln}oG;i7b z6c^*%%nvWi=YO=FHgUDB{`PyHzs=%|*8808my>gyd26RUkDlux@2lMzx4p%8 z9XEGdwQN_BcJRj9nJw;hrNv#w&QoXFTAHT^D_qEo3cFl%x9_Fkq)AIw>}hkVIKDUE zT1fob?eN^?{vy-fcbr%GI@jEM>dtO;?_GC_KNvmdQ~eaS?9!}{R+_Hgmd)&%&n4^n zF7I+}%Cr|nzT0-6UH64Ya#D28e3vzLubeg=F1h`7Q&5Ub)MmT4u|h73K27RKidnr( zC+E!WPxm=@2iCbq)*m%fU;pQ`ZSi!=YR^qC^OZ%@W4o%Q=Y;6|c(J;=?$>(8-v10j zvYY1qXE@jSV`<5v*)4_p7Hp`vdvjr-%vrfovB)!1W=i!MJ-WSi)%K2GB}J>xe(<|| z_U7Gp!JEaO-Hx$af4rW3g-2cAoMj(MG_uMcd;ctYKex`Ya$ESsRd?bv`^?^^Z`tos>%DwyLJD3f85E7jaztobPvSj;J+t;t{ zE?vngjPN&1tA1@(?6cqh%s5-k$9F<;!RE%F`oI{*Zl>WzU@07hju2(lX5Zx`l&7(QC|4D1Iuxw&Rd}{WZFX~sO zEZKcNq3`T}2KE_Wd9>V&CvV#=clEXHy`>!8vwmrx+;i?})i>Q8si)TWaqr$4zIW%L zKL3Nqk8FQ)>&(@H(?^y@@7G#$Mfx;H>$}qbF8MdilUhP(&)gh~YYFwLA z=k{uT(#o1IMX%V(+RuD^%CoyKN~~U-C*@nzeuTGOUcM? zNs60WT(a_~iT@ zryY`OX!h^UjcAE)icYte)qb73`po>yqh-67P5Lsue|PTHDf_G5nN4qx(zvl*WU=Un zNgwzBh0N@MnNnSQr#}BP;kIPF z@^+S`ekY1no-UfVJXryHoYer4>L z{hDX571b?@?W~yiV!NNEHuJPEvn_sXSZ2H8=hnEACx5q|tVmVgesrUj%A}o#PI$HJ zJEp(YeXDyu;m)^Zx3-<%?eDj)ise{p8TZyVRjYTseENxhcksJ+$DW@!X&-rI$rZ!e z-;MQLTi&lZC;FlA=&}7gx0f5O)H%&`YQ=&*mriRR$=6EUm*$n2TIs7LQ{Ho?SM5?) zp!Vq}FVt@?ExH_dS7U!$zrx1lIy%40^_DG7`w|_y@|fq=r#CVbXJ=Y1`?4>*Qt@;} z`A5z4&1OO!7j_iQtXX)&;4_=cw(AnR4;5*tpY_>V`*am=p$wdG>{ zlD)6{VdCta`^F`WZWxf4v&YjtkLcXT5 z%Tw3Q$+))e*sHnM9$miwKrc$j!zmyxMO9NjQpF`+B5Ka6R~~|0Jt1LFR#dVJ*)rvI)wIVLv zH+C$Ij8kM;Vzwz)>{P|%Tb|mRT(6(Ll6$YYTSrz{Sg77*dw-t(U5!F?hAy?OcMQOS=G z+x=g%FU9p<>Q!e7ES$U7UeeRl-}Yw9@&`Y@>v!e8e84=-b?-ybcXs`W#vJ!7x4N{Q zUmmV}JK*6@lgP|B{~3x(%lWp%)=tlCt+XnuI{jDoMDM%ksY~CkO*vcj%EGwX)9M!iu@OXNsxHYrUJYM`Y65m96*X|A?KI{>XIvvwD)ntla+N zYxF{E+fy|Yf5(KYo-zDA>&xnGSuwMI_&SumPGd{?pH^g_|m+120{fzVG8!>At|X0Xy@qEYw$7vQG7Jx4{mjJe4X?)aTr< zp4ny3A5au*5$q?D)1C5g#$tWxOtqML-ec;wPh|2R~#x+Z|e2#`^T%QX}A2!?eGcv z_}xR-9h>oOpQV%k(^=lnB1``>==Vrp+`A^rr1tEYZn4X6)~+sBRehy>G5=Km_Iqzq z43$0wS0{uO?rhM9U7ox07v&eF zEj=7tG4ZVfp9=2=+jmZ(UpGGa^jrOASmk;luNSqGg9`t2T$yBVILkBj%0Bj>uP)Co z+3v7h|F2Bm=)C%WhBK+_KZfh9yMOd&eB3q{e&jJwR{JpdTlXHtn{@C0Syli0_4{u7 z=$Y$3v|3Dj<-w=I04@Rw{HwSBnP%>Oo6-7BX(Q+NCFgd@x!=!+EN?dChp%oHw6p)u zP}4T~ZTxll&9RFN*R9%>HSOO-`B^h9>Zjj-p;rFf`#(eEF@r}+b&v$MNxAc9OwQ}> zJ;i5|O4N#CrB|)v-M1oW@qTmm#Z@8hy!%$XUwA6$R;2iZY0XN~)eG-OM(L^fl>19v z*;}DIZ`tF|)mLNHo;6>2mvk}j?L57mbF+4rtw_1+<+VFgWy;iQDXS-ymWwa;eCnQD zvCidg3wKQR1nHus$HXtn?pX8OJbT@Y`x#g7xo=!oxRPg0&&gwRy*keH^kgqLN_&#| z>zC=q-bjwUGrrK>4 zk^gn({d|v&|Ex}YaG6>kHcS7@^u2{&S1kC?a4o&&m-EMK?oG;HGZ+15@T|UW?;UbM zaN;kQ`-)$Gmb5>f)6e1;?rReA`gr+uJr|F&hkK5M?V4S9+uP&y^EXR&iO09;7VkW6 zZoYT3-Ld0sdNzh@|MCfXRtoOTZ*sr){PO&DvKzkzoH};8Wi%_`MCPX^W5w=;mM&-Y>Kb!{I}odtY&u8>oCbZN7j7(Cs{c;&~UDCo7jQoL^IbT z+ZXv*oAa40HNWM&@j~I%zG89fEGd42^Rg#TvkCXlJj}bXusgeJPUx*$r8oX)CmMf= zzgRwb*XE0BYLo6+Z9H2k@%`o0`zc}O$#q|JJ#W5`oi4Ia;+*ZRN4*)3&6aH|JyMj* zeNNBnT8hD=pq#hbJ2p=Jt3T&U&Du%L zg;P?t9V?jj^44y)rQ+MfbA3ZCPPO&gDcsRJRpEMq`)s+Ta8Btxmz_KH7My-!me$kF zQx)zKerD2?g;SsWnBA|$JBwpNR>AB^(=HrYX7xJU?9(-BF=8 zgS~Cdb{$<)a(?-bJ^N3KFYi4Q_4oF+-!Ht|p3aFDY(6n{r`l5P$>q7xJ=bm=3YUqR z)g7%^G4aQQSLgY313f1{*^>V}TG_hp)bfz5T|3`gv7P9-EJ^8Y?%K7YQu$jVrrCuF z2C6wJdsOhEP0xUMug zb{-qc?_R6pJe!PE#Z7a4_emTxjVVv}EO77ATyb)zN^kf#@&4nT`%abJJj46fbMdO> zo=z+Gp2}YJr|;e(W@FxOQAKs(=AAg>Sc+#?anC-KpA`(bsJ`^hDrRI0d7-D}=6Q@U(@me}UaQT=o8-!EHe_So%8;nkJLG=E-< zeVm;%W!dWJ;O|L);`ZMipL;{~6x8 zeU{mHism75-^lvAL;V>wl`Q|7V<@ z6`#?6hS{!OniUi4jcXt5J^RnPv;gDPb=hOgVW=hx(DOHZ4PGN1KbUfZ~pWlVp( zN{ziY<+IYt88basO#7_i)Ny9gy1gDx?#+7rE3J3p)cnBRcaMF^{qS@1)EJr9Z9oLY8!zQedav->dUT+rY22ko*cVswog>i z=c0!(9na=#nwRa^5$4>OzFW^_kKNzUYhUf|Jf6CB>z1izs^RtVp`~#_53i}FoLO;Z z-%agJTLdR;{JgDn&fBbae%6mRYKH0chN?`>c=6jhQ2Uoo(YeK^YV{9ZEUm7+wle8F z?~OvY7~?6=E#13>rl`8>ew^&TahgHm9gmv@G9C<%9N2{zEG94>X@FKn43r3_r?2d^ zxX88f%7M_nWlU>I4MdYyHmsKQVU2i|pmjW$G0bWP)g{l&`%&@HrKUlfSgQ+WhpaRm}8-G0S$(%zpm4`*zZi>05pCc2|b#-H2YZ&GPm{l|NoJ zE{6XM9*I@WkBBxbKIwC6t)XT@_~hb=YqQr*$`y;B@KaoNc2ieYYzXVOxrLAOoX`F; z6tta{ZMAm~x8K$6G0V2qi&n%RiCgzLVpsa*Ip=b(=stgTdrD>9oj=dMSzn6(RPZ)y z-#zc7(5{;3t=F$f`1I6?bf3N{q_b&F+Zm{*~X@lGrtPT#ks7%lec!&uYblD>tZjieQ8nXW!QU8WW^*eO)ZTj+f=5V(mi_P zRnmkLt3a`mBmJ3%!G?W`6hIveay9rPaz- zaewvTsg`x&SKq(9*23<*QMt0l&*0EH zO?}S(_ac3ksS|f<2A;UQVq#%K`~wk-+1Jh*6)8WunaXq8Y%<%4NmHwMva5dfSn^)| z;(dwp*@=^$o?6$>RqPa;Q>-xic&6*t8`s;9{I;p)`F*iF?vmc)xTj}24@vFaJ$ZMY zP^R^SNxd=JZfPeqF;#D`&J}igdgolgW&P-r&unfRAK%Ggs`2>tjyZa}*XHyd zXV*A%x8ibEk(Z`sit|y~<#7-46#_2tX8FbmeO$8V$(7pqE&D$G+0VEwI;j7lzy#By zv0C4E#+@?xy)-EB72k||?|yk--&&{~eQUCP*6aOm0{0s_v^;-1f67;_!?kB@Pg%+> zv-7*P*?yn+ZTq`nGxl5$PMJNe>byey!flV|E)_GLx+-*OeZrL3b*IgJB2-TFoa8hS zpS)6F%1>kS{g;<^sz1Lu?|iY=)(=1S2`-46JZJNd)pK(`^Uk;|9emu+S4;2Lao(N3 zzTI8^I4{oVKf{~sJ??E);uCH+y36{Q7jG=*{ut|=W12bpnbB-lT}3J0$%VJ~G@b~w zc1u}Y_}<>>m67(kd-Hp%m5*!gmznqU)h;FR1Ka1Gi@G;=@wOjRcSl^T{*mjnF6fh+ zUeG7ypk7a*Yrdh&QYW6OI;?(9Pshl5x24N`ulaVDqAxG=skzXiaA&&i*B$FxTyF(_ zQ4%;-`!a6dow>1PKjS`M-WPaTH*=Qn?y{tX`K=o3S(H|a?D5nR&tKUkthL%K?1A6S z{M?V1ltbHrDN*ETUMMw(iOeOXU^>NNL*^M@21Uf{%U2TK#`-T zCI9`ql(6h|+4Y(F!MFB@nS9zcFRS$Sl>3@r`PCvmKDhk(Kf|@Pfq{A-_kGivEzWz@ z$~Cx6Pg>(@Zo6il)(`WCmuDv5nA&)3ZvTVqt2acZsIqEC>Q$TmirBnx+s75twN|W4 zxiLL|kKLJ2&DeeI*Ou!Z_PV6X9W9ddSYBuTk+t6XddIa_$hvQwI&a6_*B^J6?cSkt z)XH9DiZbt%dn=uzb7LJM-iZhDT)tWPOjyf(@}l13C#JsHCn?yGuU7l?+HZdEFFyHI znb+R_DGse$S+Hxy3%#qUr_PqVyK>!7JEypYTkUw(+Xsg?uGu~}?C0e(-L}(I+~4Ki z@V>fSefO066$f|uZ(6!kPf^QAF>>%=>Nkak1w?o=dfj_7~lg z{WCY}%v<%lw`Wb6pX8l){qa)qDILG=o|)y>dikuobinT1w6tWuqqBY2Sx?@vLtEl~ z)~&pvsTxztmt8IT`lxeyc;X$&dy|&$nqpfX-uq~)PI=4iOK&&n9G738xZ`io;=;m+ z!kgNjmA=cwYQ2{SYGhCDG1+}5cH{DOmrRV8R&3w2^S5tU`RzUL%pU}_Ecsid`ZVKu zd*q{;W`{Q&{>YQG^>@u|wbzKfxhwP)~ptIcXRQ@NtfOqy)e zmYaO{)X9h&A0O?u4AgJlymjBvEnn^jT-#70%lB;S>bqeDhHpOJnf%7HO!uAdCBv@U znx-PNGj1LadVMzbP%pRXpXDmc7Oy=1`tGT+vV+?KoqO#r>L?m5`5F|e8YuB*nQo5R zrR{gVaX5)9J}sXtH+}izjH>#wTQ_zrH^22eeqx^YqivN7&(}O&R~kJ>E=Tvyq&d6S z#8kX}_x9?C;|GuH=a*Hlj$I|#6?LLmc&hk4h3KQ5qH?z{7e0>M_a%m_sZdLMx{>(J zyyHfDI;O7NpSRw3leg}QD+LLvyyEWSihd9N9A0a)d&>6EG;8p_jW-|kLvEex{c^}= zZ~MJ8jnl!qJ>zHHniQR!#{VSw5aWhI}Zf>0nm((C=R7_8&i6{6mh|PfQB^xS?ikbEKAc z*p4@0ibrRv+Aa=U^=!f7GjG4BtjyO{ed;;U=g;oBGJmzMypG<`&$7p~@6>*+#b5mm zew{s4Ug^HV^2RDY3(4>}-*$h~+k4g3C4Q_u-*?x(Ymb^r^&-!&nr-*4%HT~N*9;yn^fT=}yS4IWp{k%^xJ@wjz_1Q5_5aK)^$Qwu%FSX zUXRSJrNx^(m4mLNr3$2e`g`Qg$619x#W((%FYu*$`mf#XMPeUIPZw=3)v{iFuKSIj zcU6A!O^>8>qqiCn5X9tpl_U3YHs%9NBfHr3+klfJtd=Kj#$oD^NQePh{Xt1G{nPe$o= z+NE4GjnRqayIZk7cDHolD&G(5%$90y6+fJ(c`nyfV*7>_m72$5cON<1DIWH2l}CwX z%Nci>vyz?h>0*zhRvCG`Te)O=GS5{Zmsz_lidH|B)+%h8&HE_xc$A9OzPrh;=Pzu! zCbn2q`rV!MjaP%>OgF7uF77+kuXmMs-plnNmhP)<_S>aD+5LI{lEZr6jEkGM>wP*M znkV#mN;;?0+kGla@7`&bNwm{&v;S(CrGnh{O#3d!wZFRBss5gH`J3|_*v^%_{^Bft zy8QKZey&=R`tS1V|LQaUXLxv{ZEc+XuKV^!Y}|LMPEVC6b9e4~Q|+Vop6R5{t2Ea| zXS7~#-6rautS`5o>@1w zoHQ@KE6L7$p}*kU)}`qs-O@gXPHrv^IQ(9@?n=ts(|(oW%jGPpGgi8J9^LA;-Q3+L zuhdXhdU06V?nCW|^%INqGak-9oc*Y5#(7zT?5;a2r+?z$zizobH(ONh(xi6DPSb7o zHXXh<{m#zxv=%MtixZx>$ppWST72S9_sN-6+j95%Tisc>3YX#YxPYJvp$@B_tU!M?PQK`GTzJ=^oPP*-vxyxkkaqoat*?n2Ng|pXZbF2-Gv*w&Saq0EI`#aoXd3w)l z3jR7`8f^D!_hhy0y6rEdrx>U%&nV0FPCcoo{%W?;@B4M0;R&;srxc!7+ngsOdoeJ6 zQo>Q)Yqt|4pYfdSTBD^@cCG){JNGXC(sy%3Z(Efp+KMbv^_;7D^dxrs9s9gQt9Pteaqra9u*o@>?z_fq zOa79*!t32`P4~%PEsmXwnN;*HDu36>hh0UJwmelWGW7Gi^W?FlmtprMcJ-s5a?};J z9r-L%_geInhMIM3W#5y~$(rk4sc*i#w>>pH;Y{U*B@23bE<6%i*XI7rNa}RG+&9yg z$xocZ-XFXaI4N`agUq+?JAZ|(UzWYAbZ)%siOtiOra1ateQn%(x?JhImvD38q>sz@ zt*#7vWox~8`>}HWwWVU>m2;m>?-O)hx~OfZp0T9fmMh)fmUhQfZB>{nwI-Vd_Z;nM zerxf_`;?Y}$(y^uS{4;zPs^2}r-Y}s?mek@?ZxzLU7Ne-YBry!5`CBY?9FWnDc#oR zF}qb(Ynd(N_?ls6 z?>Rj5Kk@Wv^oi8#+S%9d9%y^qk$d!D+o@?&)?9Jj@@MlR=jmUvqq4HXljbZ}-FYrL z<$7mc{86vv4RecLwfRR>O`$s7po^SeZEg*QfG5I z+x&GvgYT0bNB9>az-cJYPxmM6Jz=4 z-pzb=2Xc4q^32~gS?y-0xL)4Y<9EtGsV!cb9k^ujE#J)(zFK6~obNAsy>#~4Ta!N& zzUlu}W1627YP{Y%&i&-`bFHr>Dh!LSZ2586?nXTA5(;^$s5hM{Za zp53|s=&QY9#qljBx^rfHoo4A8Qq5!bD=045;$3y`*`)7$J0=94St7%>Zo!r-SGK>o z+w}SDMEU#$H_jG&w{2b;cX`>1ch1QlJafB0sZFiXTJbS{Y0dBHm+k5}8Ewsu?|HNA$PN52q(yf4yVNu?vs?)mN_G&Zd~#n)c)KsjSl2C8dfx z3x8ja>Mis&>AtA9Lg#m@p305WMJ8>3CyCU>&04%NbLK3+ln=#kPu+8odwy%T%KV)8 zuXd{62so~~Q?>6*Q1(Y)9-dzF>6CN=vWD)9?jcf5A@hE>7aRhEkH4P5q8$6N5! z<~P%(^Byw1y=J@4iRY$~&b^PfZhEKp_W9l`w>#sbS(iWcs<<>caFS51(B!}`O2%3_ zHPt`E7x(va#_TuuIHtMnexvG^eObTGO<#~Xee2v}wR38dd)ckGulF;1d%Nqt<~d(i z-P75(Zh2?B{fra)W3lC7yZEaQdyXuwEh%2TGnzYPk@ATtM}4o(kr2JQW3#YSc zb=`bz<`c`$??3eR(oOv>D*{E8QoVd8#oo#ey!B~KZ?x=X+c?QT`!=L&J$dALblp8p za}9g7AN499gD+2CZYi_)>d((qLf-K$j^*&uUIi2_T;nEwfVbNkImu%bWJIOn1QAy^>mGu|q zU(%nxv)*UASLJ)7dm^IOZS9pl2Ws$CN}C)e%S#?xN6 zqiz>!uhX-1UKr6syrNn)XVe`ByYbeWQIY8tA>+bIH|D4*!HFNIms7%~cw$D6u(&N(88@las+*ajGW;!kSW$Y_P_CNg<-*5>)7I_Uub(wbBxkSR{j&E5Pid{(>h8AGx65ng)^JzV z*sWgnFONt3bX`|MiqXc+wibT{kADxZ+iiSU-nVw&#H#C;4D1DmT71R&>&*1UCGWqKcxsEE4ZM!s={qDZs`b4Puko*zXdkSxpuike1 za^rE^^(~p^o7C323rD642}|+j9Y{^TYM7C>>6GHVY83)ZGE+He45pK>(iyn z4lbVAo3_LDZJXt!OMdQ&Kg>$cYfi7`)<5yoX;PE-#U&~Z~R&D+zbnyP-7b^EFN%>B;{m!_>vo@RP%_T}Zh z)06g2+{t-!$Lj}=d#>}{-6QlN_h;=#b>VG(XF3<|)a?lLKegekdFbq9X*1rE(le90 z!s5M`ZeMcde%qod8}J29dxaGe(;t*r*D;*@_HtJi zt=5y+8*%!Ci|Z4mPlDnzIaIcJHZN&ekK*8>_EnS=WAe~eBW!1exL!GPN_($0)?p3i2Uo5kCa%=zOboCY6*X(%o@KE5Kw|<46jf@tp zz8l#ZdQ|emX{9-0S7qKs-_@R}YxSQ&U}xB2-W{LM7w)b!IjyVcXZ3nrHqS!dZMQR& zqwYkV>FDS8p{G-f2{L6|JAS>&v$|QQ^3UyB<}4 zUU)HOcG$mX)8wMQrW|feiTGP)+Wqun!7Op*JSiUCl z=GM|1!TmDUFXQ(1=XwQZc?r&Wwpim-pqJ;4!zceq=jjQ*5&Jst{?XSfm)*QB`pmkv z&->eT!PBd(pB^bY)^z*gBdhm|yfc06<-7%#9S=|SGn!&`>CTqyUuvrtrs>3nR=zXs znf$0{dYgXQ^Svgs-iBmtGWV!je)->;q9B^Uj|>{==jBkL=m|ccZ3%&Axv|{`K_3{~5lmd7S>Q@-_c$2e>NtAJ((?@7k&l zy_p=@tOlg3$;-{_OCdLtA1d#h_UpQ|b!FzSw}BhKUOTfWJNEAH($t1$7VlDy#!gJp z4yg!EeLOA9@S#VQ;kT39MW(K_3(}hx*zkGE$4`1uyVDBi^GJP;%C67l-SZ{w*tL=z zTW7y3AM+n;?-ZC7o_p@jTt%;=zFO;fxyz5r1>OEwZ$7o;ZOxr_rW3ci*)5MfuxQ#) zq!J#sqx?$mp2vZ6_byhyYc{JiBhOoJ*U7b;qh#|=p8j=aOV76F;oU`D-Y0i?AKUgb zIA!TIyP3CMnd=|BXWYBDn)g)2=j!=FF=flH-Q%9pcQstnO|Li9w)tzg(ie8c%u7}G zlOI;D%1K(enJ7fB5z{~G+oe9E`+k=%PE7n0 zd1c9(%Q{C(__n+|m9sT9l2vTa;VY9Ig{KMG?%vQDw{E3+$Gt$y>o=!opMUzQA}Y-K z{nB)~JKKKV5$OK-c9EXg&&4aO?rvRr>q_aI-Fn@-A|=g}|AgNCc(N=fg>> zeGxAyyXD*__FepEp3~%&d+$#5^*&|h7)jY*#=NDDSd98dEQPtP2e(zTJ^VDm4h5lW$S4O>$ z=4LZj{;aS1)#^&z{JFOJ(!nw2i~Fm>o+h5_%F2?_)h=2yD_3pSZD$sV*Doi#2K@4w zGCljzik^L(<(~Qzl(f4hXCJGw>{PYME{dKbzM)w8!3y>8_PPtUMrU=iMg{{wzO! zb=C4!j&T$IZ2YJ2^6H=4hU@ZX7FilDTKyoj^=aIR)kWpP`Ixo)g_w{PP*yZ)HeUEQQTOSbOw>^F$$E#9(vPyV4dv4yQy|I92e*S;y?op$-j$Mv_kPL+wv zSDcMhnbXgG*<37JbnCYhb1F~nI3{uXNvovUYs1BNESBisER)ZywUU&4B6EM{!90nR z-wN#$Yl7sy{+_Y?=C=ty5;MN^=%6^lAFFU-ZO<)11-4lI6muuKE|e z2@{&^-R)9(+(c4R1Q!$Y$%co$_;b=IaGq`B1d> z?soe{PI^x)R)<`@T@$eS-kw+2kIgKMzj%vXElPjM%fI#$^e%R6n!T$&{$bhbja@s= zifC$VEL!?`+M$I@H)W=5Rk@||&42TSVqIgOUB6F0zA`g!CMeXcQH#XPG&plaqHa9n{Jncu)>ex+_Lf74Ww&~BqXK#<&o5`8CyneO1rnv9d ztEc4wryeeG+kSSFiOCz4r{#gO3csy>e5B>~nJ{N{k;l0nrPt5zDVf>d@R@JNrt76y ztB#cG-L&WaP%(Y|qucv>zbt#MclLSuhdWo-Em^Mdwsl3Q_wMeSM{awb3t1Wzcw_d| z4-fV^_a@rRyE^@5=wCJM-YcfF)N0?kXERNjwRij0W6?~SXSZyw(~(&6XZ!6;wnvw5 zmmXQNYo&{O^3&bd*1RsukDrp5TX^;4lBu~TwYG20F8MTRd-Rt(fBl!vnm2c8#NNrX zO?waLT~RH1vZLsYSBybZQH^2u>VckP^fL`2ggUhBu5sVZl+ytYr<;5BK< zd}ptYPfbQfMxQ23+b@w&@1Oqk@gMcM>nB(4wX)R?T7U2Q{uvej8P>(~|7Wtp|U!3>WA-fTc_*Q zHqZFlrFgho%+BZWrxdSAnNHG9AzN3vxVWV`FIKi@s`#$3K5BK#^zHpwbw_6*R=d_Udj`RyRmz=m~{##FTEagQPRxI=--t7 z(-)k=6BZU8&2wM5tIp-qhldl3x{5+?c;(p?#oImBOW3nosPCSmYub#9#WqqYOI_Nh zgdQ)<&Ukc2?f$pb3hUm_3DK;*-RZbvi$&{}N0-WTg(LSaN%?sE+(z9={q4v0@Rk`_ zznbrBa&PgAZn4krw$@agmirW1kek~x?VNV;qDQ^Kt9(r^&a2ftzfLz_qVV>TTOt;9 zQA?!KU+Wsb9b6xLjxtcLwBs4jEXH3Af_vTsp zNB7OVG_z*Pz4)xT(Nlb;oC@pit`QgBJz2^6owcr(Z_BBjdV5ZvI~lzsWwY0mM^m2% z&io#8dCTih@AV@#TmFbqy5C;yx|w^<&(mI;-`iS;UHQ22^0#T9*5B^R)c757R_mhb z?jr9En|DrE+mU)K%ROrEt*H1I$G($mE%xaewVvKSvu&n_a&z-TpR*^_wjFwGaZ%b} z+LYZt<%6O+)jxbPIcq1Q6}Hy-pp@?YqkZ>V!lEzkd_AXV_sd&Xj5kUD(tTd!wQk<6 zx7QD?-ni>z&g~sn?;jPPd?UYOS@F6>8E-Q)BjrPkCp&d-+Vu2yc;!roeP!&mULgf3 zC)ce$E-^E#bHe;tzb|{-jVu@By*^>b`uTD829ev}M=Y&6T{2~xrTRXTJJU`r`}SjM z1^+=yECI#_g+`qv9wUr!pp!*H*lwa8*ieu(y{zP zYxPw{TsKWOM@C(~QM*-jd*{|!B2UVz%Ja8)D=VAK{1PbTzo_HH!su&xH)d}yHC3sq zUY=E9waujH%kPOh(>(L4L(49%efhI|?dr74jk2x<3=GnDerWxDdzbS+Lq+cW&2|T* zEibRX$P=%7e|?ON)|ZR_8Sd2W|FVbwKSO(xuu|Q}N5>AG-L#jvcGH%9z8f}XON8!> zn{(=Yp{Hbs<}}0Av1=_Y)LdRh`t;k`If^Vkk!QTdrf5Qo&Xs*9PNh#=_T$-XHN8OuQ2_?q3(W{SdL!&dEMpE##zFa5VS5^l+=~s~?~Cy<$0k#;1*X zTb5=$TRgi+c**3hf~havLMHjD&7U(lI!Nidj!o1iA8pRr)wwSDN0e-4M)CGSBf;6+Ubmc=Em4h?nr`$`DkZ+U%y`c}BVMmK zci)Nsf?Y39+NF9Wu4+GfV`I6z@9f)?Crd_dnws%cT=jTa->PYg%|k9Pm5%Z*R-aY9 z{YZI<`>o%pZ69OA#j||tHeM|aUUKI6hESvL}ku3XOa z_StmVXFF?gyvdWS>pPZR@(eQYTgCbQ_HFfB-ztS{6Tj5I;G1~)u4(i}$=E4Rme(s4 z2JW0_@jJ?Gebbe!AG#mCrskds%H6FoWs=9FNlIQG9UXg)xIR(Rk4WIpyO#SWs4KAV zNMF79DI*wR0xRe&l z)6;XJJo!o7k^MY#DlOu-t7>fBYw~Jl?zJto#xqLh%;k>U%+sr`cjj&EcGHM+cOE^~ z*soQ6RqEcXw7Y*4Ki<7iZ8fPm{?ns(yPe;0d|Y(hdhQ*AC$^>=RJYxJvOBHFP2h`c zVZv*}>YIh@`ec)23pKk9uUhz+Z?xHbh1WcEqqNnOslARWv#S-=ANoHo&kb61+vVnC z@tD2*J8v()wf3+3q@24~cRCeMS$j;bEVN_WO3vFi-UoM0oIBNWtGMRvrqIgzEe9($ zpM3SAlJ%bK0!!Xtfko@~y}K-TJvwcdQG8cXqMq!O9pxWqsxS04mom}W?Q46>e`9>` zne`7|TCEBBl=C>{XkJl^p6ZiayW=0ureE5*d__%TS;a58rE4>$KM%FIapL6RDJw2z z1#Y*_ecQD*?8mCN?!x)g3i;b5wj7DNTll``#uN|Tn;z@bzkXZ3HpVim5a4Y zG+)oo`g5q~w$6?xoqbl*CT$N}WT&)cuGHxsF+1Is?W)|_)o((gqf0()`yKhU>UqGg zsVm*Sg?#NT51X=Lc~PX~bgjiAuPt{^3Ta6^a@)(GC)?v_aHPG+e+D_O-qoEcHmMI% zey!VeU0}uiV)JVM$c<-gekSRCUFlY4uxi6|TwT{kUoNqe-!^1G~4|mVB%d+xTl+;_^lF?9&y`&DVRi=RZS>6UXTjmF9Eq zZ+&B%`Sh*Wz4qnqLe_cjCx6(q#Od0tOOLaH#9CD|qC_&I#3n0yO&87Dgb05t7 zn`7}#M7=n=Ow!^pdG*4_*O&Y+a?h{~k2~>f-OQx(##4&Y-q}RgmA>&j`SZed{@kkb z!fUs3-Pta07C0sGp3SS1>mIjjY_!Nf)4XJ-Xw%vilONcsnQ?A6G~JkSMLR-RamOno zu2R#a(}GVnJ>p+{(m7A%OsATP^){2cVW+|$CacR=Jqp+E6jYiX3_U85qmyoR@7D0J>Gtk&C$9~ zCcE!WrP1<>k4z-o6sJGovlJ3Jt`wT^BQ#qwEi~0kN8+@a(xC~jJ2ID+yjiFg5>&;m ze|ySfnVg7kE8GO~%U&E+S9`Hd<=&B$ZP&wh?Vp`3_V;gb+`d_X(_H`b%!u|psjj{K zxpvt?am$~V<|K=2=H7a}-p=~*!rJ|Rd`oz@%AA}vzr~{Un`7fm*HcejmKEz{Ctlj$ ztDKfAsWtDZu8L0H-xJ13X89{;Rq)t7J}WJ)+pm!O*5^~bRP#giz1YV`k9paY=f!SYF79#d z?Qsq#yPTL6{~2l+w|%jDdGn9&8Ix(BE?c*TM%=r1(X9fJNi_vUYX#Y{i@DSF3-;PjrO&5D?TniXmM)l!>m2J?{B+5 zGCuDp%RjR+KJwa@duf+TWv!lG@3*e|d2DZ@U)P^Jjoll%^5jyJqb_+a-8Vy4VB^ad z3=9lxm*=s{On$s>(qq5dEAIUM?)6GL+xD^EwQcLu-`YqsZrrdYj#9DIG<;+p4gQgDRVV0 zcJoWeZeLaIZvU$2t@Y;8cQ=mQcsz~S>zn^tW1oEM<1=@*Pk)nXtQEWK=$f_J(VIJ^ ziuo)K?(*B#K4Ga!XQuInQfqZ-{lM+zVo&sLze`%psj2nRKQ%aOci5rWFCrJdu8V*2 z=8SALzc#n&EAwS*pG56U%j3Id6%?>j;^fgCCpCk5m#yA7W0PRweg#IAij}A4u06c> zpy!USCfE4RF=wwi@o~1PRmHdBF7L|r+`{N*H|;(eE|V#-IMW6?f?jjkz5JWXn^r&9 z&%Rl+cg1mg!K-<5;$9wo-~BE4O|{4RHMMF_&uuD-{TK9%W5>C@&uo7zy_bByD@!~v z_P2D;gKvI(fh8r`Mf)^<9R9F)ih=g)lsBttrMJq3L`?n~_F~)G!*8Z%>OOVpl@~a* zW?pyu^R*FPxp7^0Yqmy8r?q;visq?qzR4>+`-^YZmDH>^x%W%fs9lMTPpV$}Xz{vN zOQYn}zv^E+ze?8V{Opt-OndkE4SeDPK-`!c&NrlFOai?OU^CT^N6nf5Y zm^VGASKH%?)b8oMy|v#gZ?}BEw57HIupQfAAfds9_2oin0WdHK&sep%((>34Ov#Epy5G0D<}ahtrezuvq$ zeNFN6z|!o9U43b**S-!Ycm2Kcd&j9e3niU*m21uRdy=hTqbok8?zx-g@i94Et`%vuxe8=4y$9r&1inYsVY?-8%htzPre zN^axS#rHP1ti8XvePYp>I|gsuvcwbf0w(uN?wOMEBV|@e=B#r+CO)rlJM&TeXWhYl$eq?pHEA{Clxy%f^F3*W> zvW|PxmE(6;9eHeRRJdf(W&bl>H?9WX-f-`|^Ir47OIFT}%d>8L|5(Z$xchk47uCEc z+LPf_=z3f7Ug~G^ zmXA7|RrgkIH&wfMGt$*fwLkK~MWG`}2aI?YE6-+IX8TPe%QJNC^9#k=B7v7;pBM#y z-tPbN(%CouN2QM5aQZvr&%5GDT~ox)iQV?@vYjXK#9E?2$#Hb-=`zPr{7!D7*q+s_spow-U{cgYjK^@TTHzK=~4R(`yq zDzAS{(%NwUlgFy2&bSm9o_pHy=Fx@bIZ>i`nm1N{e4rF5Pc-=Zc=4#O_TMJ`c_HH`j^eXUU%KaIt=MsOz8Y z+=tyWRu~rhXq~+AJ~DV!pyHJy8~W9hjTKIq=<1y9xw%$5=Z@2(a}n3ye|ol>=eKv) z8di$C}YQ zrQWvO4w=*)eXwx)5&lk_?Mv28U%Bm{#y4NfWxnCm2d-y$Q$;>y`rmxS8Iy&R-!M&W8`dPPvZO--I!_`dZ<&3Ci1{KY@^ zcSjzdbLDuMT)p?7@bvt9*|*XwH_qJU-YgbSe(&E2<=+#I8Od<%Sobw<&usmjtJh5qPnMkAxH13Y zOO0>Kc$L({bgxYGm@x04TfOYHoyXbvE+4%VwRgsKo|v*jwq3nBlYfdzt>=!ObMi93 z+qtY~xlUH!0~CvWpWg7^yIcBwv)ZT7DL)qLZd2U&eY?JeuX@C>=v2wG$9UVPZ-3a^ z=<)oX*S)s0Z@$LvTX$n-z0#&rcdUHc-v#D&CROsq*3OOyx4Py!E%{TT$E34ew>O4% z_nW;6c`3Q-{nDqg^Yw4?bM_xIx)d5sd?M(?l@Lhqo39iA~>CE>#FZ6PE+1y zZ_=JSIoe8hZt=TAwI|NmGM(aHe`rI?quFmwerrejEjZ~Bxa7pKwXX!qKI=JNTIFB7 z>Z^R#2jhGH{7?1e>n5cf^mHprx-=ywWz}j=H%~94=s8iow!Ii1e)7XT|83~D z9q~&)oH18hQEqzWhYydw?IFJuw@p#nQsR7qx4o9DE{j^3AMiLR+1E?taNEyq+3zR3 z65e9dx$@!jPxnmMROQbw!Vrja|tLyNR;1cMBhnn{6gE*ZHGlmeig6C2eOm+*_HNm~v}# zckZ!Bvx+6RZXZ5+XiE0OlGV{yg|oBFqA$ohKFbvjxU%)minxsnUj=T?x}!2D^n2>v zDeEpX+sf;_sd*o~I|dYtd?9_XCG^-%mc=_SAgpgBNs?$C9)pvzNM-MRHaJX+?^#yz2nTuqY& zuU}p?-(=GL@O2NT2l^I$Jw5O4^gX`j{A?!2oBR*oem&pLyW!E#;xit5*K6jxc`jV~ zy({O^RuH#*{l1iSI3FdA5I)F z4RznKt>WFvV;|Rag?Vy3*=gi8Hz=yoGgwFZl&Quoy}2HzSFG4Mx#;QE+;=)RX35FP z#cXf~QYl_`2(oT;{PDr65_S*^%-yE^V0=65r;=dGX4lz@YB#JEwEI zUhOFt+&!~8W~H9jG{5B=eoe|X@>_FlQAWp}dBLLMMR~DJE!&mOH!n&`NI9)`w&H2* z->DzOO+aQZ(%*Vbe&Vd@>AsTr`YTs{n|#-1sr;8yQ8laV_N{o=`g_~o)iG0h-;&GMuYQY*TeL9!Ui6F8K0p2aCa;OP^l83t zzWjy^z3=@~Za(o+6;iQrne_65ZTs|veX4V-qF40qwpqRG_SDp)`r7)Uv9`H7A}R8l-t^4Bs=RZHJ%erxvMIKH`;@7gokJd$q~Pt~n&yJt)c@ZEGjI6Rg0ncniJhW?tC`!w_Q#d2;R-?HXb$ac|@wO?O{UyFcSaVCu^ol^d72h|F)_vNdSO z+g7`mJ8pjqoK%-Kt5j{$&-WUuBEL_1liF9abh+nM%OypnRpQL+gT%gMnsGUP)%Pkt zk$!z;S*M+|lDyH`z50T8)2Dtp?Q5xSwnt~T>6EV}XVgqnBtFdaEO}#aDe(Hk6(@Gh zoUwGuQ}alxO~2FS&e>Gx9Cfo(Za7=ulW)6Xa`1$3w@Lm+%WlkA7Hv7<>YH9Zw}}_# z*e6)3x0g-!$9?64!YNBH*NO$@`n`(d znsYRdvG3Yiy(z(yURjmIG{61n{oq^8U3b^J?rN`=X6$jj?f+yhHRXau8BdWHiS~K9zdu>+_ST6K=Wf5rTFPr4^?1^?r3;rQ&FWt{nft}0 zuN{9{oF3+foN8K}6&K8Z`Ux+)mR~{7+U=ioenwo~@!9*WZqa=GS7Mt~{xdk#o(?;i z)#)m7t*Ut8?fD{iPp)T9&5rhr47h2|r0gT^*S2IyZ~E&sf0k+J-QQh#?a6GTH@xcp zmlY4TW*Nju<$gUEQZikD;j7JIqv&A$ZS7H!B|&17f_(NS%Lh(b^kdPBbGxtQrbL&m zclEk`Z{DTW`D;I(Tpe`b@@Iod_hiSi+Y@IcJxWfz6=t|lA}Y4y;p0bB3Ug|=CaIU3Tw88^ zDQZ{Q^iRicey(({*=(eqy>rUy`~y*IKHZzxKmW(6v+HDbZI9afzA}(2=obS61LLt< zAr3qH0&^ta&r}sUX}j2elfL0seT}!9tDbfi-Y%KwxZ>rEsF#}Jvwd&-w=I`g zwxlxuy6ZccLfcmDZA(l2S3Z@UzUlq_e%|XLUAD&4x2#oqy|*#H>B&^-wPm6V%k&c% z7z($Uie5V_ZTx7fqFr3umg$+cbzGs(m1VcBkKOcR?Y5bYbC)H&3j4k1cHx|w?EDpH z7yn_Xu`adEQ`eupbDbr>(z*1M@)`T4r`?_Q{QASt5I*zx`dQc9CtaGIt-EE9&kP<< zCG&(M9nVWPS^5d3&zto!>mT>^;-IN7eUDu`pS5^X(09MkIF&s)pMo#f&zGBiw(zC9 z&Z#n!(-zm>r<}tOL+|^~fclX*(o_qAjzH+gT{~6vlS}M-ZJC}Lt(QNaw z(p~j^S7y}g=EFnZLLJqg4y4E^#Ch?@*^a^sx^77ucZa%Bm%PVJ&SeO01ed*DT z+v!WA>ZUg@iwY}vdrZ)~)@;AFrQ)R+KB;?6>ptqLowBdzyv^bz7w3 zx|e5soKm28VEO)BzmyX3;EP(?!V*O$(=NT%TXJEGM#qU0e+$2Io)QY~UNZ6W#Qt=x zGi&0zrmoa7Ipu5hpF!NKGqWKxD8$Qi>D7r7Cw^7dYk%@WJ8tQ;Zl)$HxDt6TF-A9gLW@UF?4 z_h$d=j{@5{b_t*SrYHXO=u^9prS%Bo|lWw(5J`Kof)7t#H_pESK(j{au|`=u`~E_*Ay)c4{aE*qQDw7C~|yw~0o z6SnGg;oGw3Hy(zYO}x2b^7;)do->14Mc6)xb-g~bk;hsqX50CFU(_S}CgyG=X0^;7v}=~d#NEuX6|-Luv5@uzi@9_K5% zZuzunrR3SW)iP^u&Lvb*xd+C7WHOXDS{-L`9cH0N+t^SXP}R@!9kdv*F@ zPqnt|QMEm~Z$7Cyr|#wZ6}ary6Pwzyu*dJ;x>{dAHO;K3RvFJ>WqncY*MxOGE zo@%%-I$CI%a=}8g+~fJ{Q`r`WJKNh&`5P!2yW9Hmdp)a@^BP|MsxzC}E92ww;YQ!n zJ-?3!MirRvc$(*QD01G-nG4R`E|qhaHvat0|6$jhn$&fS>8D@>eHQdBWNo5`p1WBs97hl`R<=K9(lUisGSan|Rhu78D$ zK27}Tead1=vc-;*VLER&SG}{nac{G_^u|)(e4pdf{ybh)=<)uUg*D$!htpYAuhwtL zvkvoA4&HpjUj47jpJhuX3PpVu6!PduvRXIsj{3x&eI?u1v-h8TBh7ol*0pNwi5I_~ zOgcY#>CSh-A6)-4NFVrqdh7NeS)Pg+zVB*8>%P5f`OmP&?ETI83A}S&UVqW1KkfbX zb#kIr8UGpH*~kBiWB<=^cq6Z`P1wF)?1ujtVy%9IE=P5RT#ouTkA2a<*7%LFp!4|j z6`GV8%zh9E7PIdkM4nFDvG>C2M@yeM20L%rz_VCm*O3gJbVH%I{I-4G_ z!rIzOd*|edX4-C_&m1+A`{}ANd;YK3wU6#yitD!2cosQlPSU-bPX1l14_%q_x8ro9XC#HWKE6Uak<8DXO`I0yr73c(<@oG znNDeYe!RJF<+FX~-QF#~@$FY~c0_Q<a-!^t`_n|f@K&j+oaU)~(=IYySYiEI z6=zMK8^V6BsZ(^DgHtx|Ee=;s`fU2*soR&0pC&D?u_>Gsy;@G=ZS<<8yQ`G`Gr0d} z@H$(+a98PHws*6$BcA@AT->@dzT$e$x#-k`l~McV?zFc&6gK-&?y;=3wi!0Q7XHlz z?m4e>Q`>WHT8mc3ZPS_dx12A?b^n@^N%tdNX89#wT& zHf5rk^mDz?t*0%`a+71H-VWXxX&Gp0a<@$PVs_iq+-tl2eQYIv{hfc^S2*a3=^E*% zx-z|T))i6jRr4x7ZDkj~7xPQ?#+~rcFd62Y3rX|zzOe$36?up#zne^5-(EO&#pU{u&m(7<+eOD{3wx6}`)=s;K z>v?Nti(ks)db3!x?#PpEZcp?yy$tJaU0JAdJ2-IR&ead9*H}jG{*?Q(?d|Ve?HTFM zBefH*%kfIvAJ<=(wd!%Od2KYqraM}(yiE!XK+-iGw;q}<4T|4J5zj5dA z?DxMTg^ur6*)g46CRS8yx`CD@1(dr5t%b5xFuCit8m9L!>H30Errdz53j4eFG&;Lz@Hc#{-|P}$Jv&X zA?NR3^W5I?#k;CzVwb5%)N7CLQ;TBrjBaN>dtfhf&Fii3I%k{Pr@#Fy53Pv!E_G-%V zy(K9rDJe@8+Nl8hum=ZFbXWO!}sNF?pkCt67+$p~Ck34T)cCumliCnX} zg(YXVO8aSx?~3lqRcS5LdM4HD&g=C?byMb*uO>o1CvC0d6Q;EED2H9%`@DG1)-Rkj z>JNU{H{6n&_qqF-je6&tOILN4#_qdkuljJ++jajLq=QoP*Z%Rcarp6fZDFtL@wMNM zF|RL7&x>7s{N6RIe6=s3_hW9Pn5E5*IUf7OyvSDT(Z%okRVQt7ue{-&%IW3xby4xM z&5Iwd;WnRBs2C@)BD&;Jo`24Q+pBZm<{#bAW3}|f;mdYQKFglVyYgj6+%ui2taV>D zZPxv`I_zQE{c~@hI9;j8J!JSpBHM8^7(`F0j<<-uf^Dg)4RO4B1?l$Xf%k!VK z+265$>YT1sRj(xcWbJf=-5uF?tX$Q1-Q!}U*Y16nI<76%c=PMt+TzHQ-|v2VS{HY7 z%hZfX(MprooB_##>UW>nbd^dc}cIkW;gIcZ*B9d74}f$y~m1**24J zrW>d8l`HO?t~UG5;hIZN&376#U5zT7Dg8csuiLC+0&O?eX-!RC9v2{)xn(KO_u5-Q zla!Q{6ek~_W7_wA_uN12QbBXyy!2P~Q2WoYINdic`t;FDB^9wnMUN^h&XrbYAH5s4 z&+5vi(iyuR-;GF_wq2}zC+n7-#cy-hs_r){o!Z*`&TGD1%}G6Lt(=#e<_7oedNt*7 zpT?EN#>R#zXRfSI3ypbOn)^2QNA!nD?K+?AW}ntGI#TkWsU=xtF~?CYjbmD!t}2tf zlcL2Atrfd$h6pzMsJdf3JEl$9F81T=-Qqsm@Y?>HKFfElsk|oJ^Z0_!>wEoX%6;y& zxhrl*`Fcby`O}u7RcQ3dV7>N_ONBdj2WS3zv8k9{@1@N7W#_%Ft%*KyXy-oTo^)~d z(1?>JFD_-RuJYfo-iXh+*j~NqsqKFT(@Ni3m)AWzc2(c~zD@V_wH2?`l~)!lPhEdB z>PD2ug|xmEMz5SzY96oglQGwS;#u+T+`O$GZgRb`7ROJ#|FC7P+~qfg2aW}KzW+RR z%eQIXl7|;O_A0#b-sbar^-Yhi)e2v}qB3Da>hqXWo}Q{AJ~I1H?Wo!JwCKoE>q{ZI z@4j{&2$<=C@H-(E%?cR?R>PT(uKrwRoZlO{{dRA<;4x2^MNT(sy0;uYyDN83&RRaf zY9-J4DQEU^ygadyr`u?=eYQ%hNp7&R>y&&pc0+gkd*Gw;tb!^E~H zWjC%as`6en?MQtnix*0eJx&Ya&axu}S_t!1fZ?v`!d{yAs8f~I&HE2#(^t!$UnJgjBATYlw^ z`MYe_dOhv_C3}3+{qu@{mV0V_+ja4Bl4oex+6fKGDtE|p4AHvMbG+Dyy;g;2Lm>)T zr#(?s(l3>ZI=N+eT%+jhj1`W8E1ra($UUDF=PLPKZC}e|Rmq>W6D601-Hp1myRoor zY3M7-_5Q_6o6qi;?5hzodAr&lA>V3~`+FT@V z^CHQ~VY_tQofTi(3X_(o9&)p`Gf#B$RGr&hcU@IwaZt6EskdJH(-*ZiT|a{tpWk^m zF?Rk(`NKCq`t(mJlq)w*c&zUA#P#|eJNqB|KdgNwR0)+M`UF8Q;}rB*cI`uh*b z#}0kFT+b==+~dpiscY_DRohqEpVxixxb5ul&d}wrL*GW+lW5#XZ&zXW-Uh9kO9&*Oa+Rhqz za><_cNrs%e%NXCpd^>luuX9aXYGlOi{PHz=Hm1^Evo22G5O_AII(A=6wxjY&aVM_= zS#SOOl|ei9F1oST?|#VbKCcV*hi~!j@qBx7{(Q5mQbo59{XO@p%4<%}D{6JG^+$oKslLv}%xkL^jUMVOk`h}s zXS&U;NQ=w5J0?3W)pA}|Tdn2PF-13O+Twh^%RRCAdru#HT+izBeNv^d{*2Fy%l^ce ztj@JOxvH>i#j)Me*||}LAC|4^3B3{FyLGnOv18tw)y|aqdrtba+t2FJo@eP#Ko`_{ zd30)e3JMAe3LH|m^z$n&ooqH+UhbUkjK`0UIvo)eik|eteDB-0)&4y-b3xaBlH@ZI z6*JD=cd^D${ldjrhPU+FL`0Ie#Peuq9M9M%5fIpSvBqG*V%bGM5*^*FT|XRZ>M84f z*vcyLR{Ss**Qj!;hD>_b-P|p@$3>>y&ee*#wpCTbNyu%fZdTx=peajs&741>Lu%3L zJ4FjMO(j;H+}qJvy?)Zw9TWYJet4XBURmY4e&oW8vVALSmo9P=$=xw^ry+}rr^@^Z zzh-``ZJL^RrL-bq?VKvF@9i7w4}YlOjGy!?ck=4;s(I_9qe`!5oaJN_cGp;dnE0-rRCXcJ~!6XWLs=Y?(Y}n76|do^L}ve`}VDG?d&er7=Cu% zHJV_CO;$4Ae8o$|B)+4Ri%;>2hDCe8f=9AW&BLF&z_@pAd-e;zlPB_&+$T-Dvf^t; z`JN3^9x88ZyC}J2$Lrch{wp;-m5g3rUOC;-d-BZwPN}<-f|L)qd2bW4V1+LlC zyYQyq*}|EsYkGR!7Qd15>AzW`ygemrN5pScJndkk zNBQ+$KIpqO^hmF24Uy|+heeOkQfX6`KQ%4*DH?`3)~Oy{m*c~`6H{K@D|K6{=@z?n=1lc1i(j~2=w3e|Qq|HgCOG%x4^OXSwms2a z=S-Bjy^Fd$Ym^_$s~6x6Giphumv|oQre2??Cd^DQYk~KsIj4H{b8v?MY}CkCi%XtQu) z=|4ke_8&j2ovro2==aK`nrGrxF~>MlZxja3GrQH7=J_Jl5QCALZ&{kCe=_CMCMqhm@=mbKMSua^F^>Rz#C z@!mZBB`Z66R&<=$z2f8VZI>_XDm8wU^}=njvEHJK)#h6N87`))?Ge4y#CP>9=0xnm_kk zvg_+B?>D8JUT@#MNAGR^oJn8Bu6gd&xV-oKnR~w`t=*FyG<{Og=j1p0<)?-m<2ZD7 zHWhqHtPv!Kh75zK18V?d2tz1I42e}w)*aNjp5WQc`BZPdxYsIWLEYHfm!%8c?Hm<- z4VJ9bmoLAVX}Z)SP^j0|D&g0$@O?#dlD}Bw#Km3C+?BhaPW6Iv-6wBj?dSc67G0mU zI{fDLF8q9C z!~018;N^b-3y%xFvSf*8+0Xv4FQf2CRbQ0%ucUwNe;S>icgk(`^{n^(QCl6-f&3)h0LF`38AM=S>-{|-E|LXern2b*OM!9+O zUv)q4v50t|WNT}GRs6g}!Nz+>s;cT&>CZEGn7p^+%a{KQA@MeehmKbZzI^#_#lDKe zt$p8=FJJx}So5)+TlS6n^5wqP$|#x|Ov*_r{ig^{TIHUTOMXwu%a)vQwRq9B=~rYw~Y4w{mF<;Tdy9ynL~QNFR?OaIk=_9J|3wxgWUFc^Sz(A+rZ_al2| zn=Z$yr60YYuyb3!e^e>bWN$d(aqIlpFE!~iZJfJ|U&i#ed{+0+sMY*&-Y8Q3piIE8 z$2PnAG-@@~7@n>8%RGT_p+{tu!hGb4i{-*kPaoKNm2`isf5QB5^V1?n@j3q)*w;S$ z%$HHKGvG(;oGE|g!yo+PH~6*i+B=oP^--#}wG(UNTdx!?D)st!%(`IGtJw7~e(v+1 zQL8c6=!D1PJG=E??Tv4;+rc+|(!`H?U-qxo@4oz>VVUIvtG{3Roz|cJ{ZRbje}+?* z4?f8AYagpR_t*Y@{qObvCKSx6e)GZl-~PMYAFf`p2t2;;Px|xi_kX$k`TnS#N$&T@ z{`~!4I)A=D%Ew~+{juEr`j^6=?{~>?RKGuFyYD~4CH3d~MJxoq-|wle{m-z(|9qWB zf#Uo965oFRXYiOm?@z!3=lgYrZ@>SWXm9&t;eqyjf0A#%|EpA2{n5FRzxK!RL12?j z_=JU9G>^;WayW%-U7PI{(4x}o6*OhBl2?%Hl{0@@91l%Ab*d=KaP{On9mlu-w2kdC zG?zcJ#58fz#9!8zg;JhyU9!6rYgD?NHcEwa9((jzz?R#=Z z|1)r^74@q=$trGJxH|0K?7i+K%OBjhcH-Oh-0}*a>hmYkBF{`rOw>JDoVsiI5|7HX z<+6ow#m28(TwG!~x)*^Ll_)88`U%aM^E_Pr_6Owzos`I`pwtmdu%eQ)BiKr?9+eLUi|2X zZQ`hk(eM~eA6RpP;*Z0}`tRrbXL$af!R0^0Q{SN^5`pTfJoumyY6 zXGV&Db*XRv&!8Xp>8*U-e}*{WJF|uCn}66Zuqf-h@q9h!mqiEm#&^_zDwWr}87TAg zq1_Tc<(F^vvp=kKKin9|v*r9+p^kt2N96ffLyW>(eogo(KjS|G`x-kLuNn9JE5E2- zvbXrT@XnHX&#FCLlDp)yLhe#B=cSn)hw3t)-^tzbPqOdE?J0AWW~c5~wz}F;cyCT3>ut43UvG+(tv9Rq zzA9U*JbNfn| z?YvSKa^b=AnX^+**5s&4p1$UD?A+Op^RIo)-*;_q*VBV4SA5sq7N7A*BqhZqZO6_U zz9p%z1iAm&YsF8$c-N-?KSS$;f16+OFTOFkZ>o6l-ROFuHy+0`A7^hmbM5dy`AJ5 z@6#U)V|M>I`-$)ThiU33?|gmy=g{Queb4lFe%QCX{I}z)@ZPXz_Wv1#OXpwN78C!U zp_s@1P{e-K;_LmN`RCs=p0V%wpgyW-A$&s*nT#q0euI&S~aCVtY+*T;Xh z^Iy`R5&xfI`Qcwv@4r=les?)sl`-=l_3lsMwiW*w3?6^)dM3Z~!@s5Fzde3$`91rk zb>v^~)cwaI9)r}BgVof1j5++>PX5Ze{|uS(W%G+`)wAta=4}6Qn*YN*eXz?9{ytjF zf9FSi(C)uWYH!t^{W5pWzonb*Ki087RIy(jwx+WT+0!(1D!`_Jg`s7r=knAn6q4&24jbGa?s zRQrM8W63O!zs0|v&iC(-e`pgw8Js}1FR9PS-gZnX-fQ>YCEI7VcVGK^`mq-G)ET}e zqTf1ie3>#+#(6@kDyP}~$~*VGPdjSLE>a3C37kKDx!;tXGBcBvZ1mo~IDS-P{eK3Y z6DIv$yz!np?;mIO-CLVit`k4i{HV(1{?+}@ zFs=Q@nbY5IJ&)hYvJ;G@A{_;zZw31TfF{GRs0nr`)&QZnCI6oFqFTPz5jMb-8z%^FZ2Iwu{&m8 zyRP(izxS^;_iq=G9n6n#@A3Cr&+RXKSs!rX-z~cre6{}?j?{au`_GWO@YjyZ`w>=UT3aQ?TS*Yd7E9ZAz)$&Ew0qye;`u`ttUv zDb9z~qoTKnv@ zFRT1B-C1BW&$uY7XjgdWA%8cSO>I{enU)x-x<^0Rw=cf&N>XK%+~Q7i?rV8kWgm7` zeKC`Fc(q-vn9rujan}dy(>CBqr|+OI{tBLs%0A4 zx9A2RFD%Ji<{|NG@9vGk+f_A8H!ctPGsSY6&WT^u_AmN6qx80wvi$aoo%y8JYW0Vj z!pqvJm#$^iF4wxUDcoN_{@|-$i&q}lar>mt@7opq4}Q7_cgAvGy}#qvr04M#KQdPI zOukdyaa_diO26X4ZwycH4;-FnnCvT!Ux_`>fuDKp3y)jX$RGade(}Tob%p0os!X4= z=kS!ioxb{-{--yn2>z^%Z~iOzZBfvj-M&wmCqA9JC3N#U-RH;Fty$aiX6NNpt7(gm z^_}9)vwC$U#bwIGs%LdhpZZ>xJ+;(uG*{oT^;!DOqnmZQKLu>&*sgY0$a~A7?dp4y zwocnM)n5AYhFLeKb7cNly+eJ;!R3wSpTedrS(%aha>=0+Jw3Zs(qH|N+3fc;r>A9I zs@*#|zH8a@t*RVe-12$adHQ4IOit6|;S#QI-LGfFO5{z`kknKekXF~%HwmRy8~x_H4d7#*x1-u++@;??Gil8E%#}fJ^7=) zps0p=GxNCPN!}-O)59Ip7oDuL?l1}pJH0v=v|%tM)9AA#f9E0L7-P>xi{8V!mhX!dcCDT{BP?ZGLW-B4X3nb# zw|(|)m+Mx%x>8fgrDehIeaq^c!jB5>+j!#V56wGAe}0|1cWtk};_kiXU)Da=%brs3 z_1c!Ze!d#Vd`}7GUQSuL`Rdn`OPRhdpPhT{u6OUQ7K>Y#76pm5PSoV`>NwNWtA2g` z3WMkw?{t@Ko1Nae=JsBWJKI&Yi#(Uj4fZdN(-QTGHd(!VlSuJaXXRs8L{@HDk$hJ9 z<(2;oi+@;gWk%~yGrIitRlQnOI92N6kIF15 z^(xV<{KhgVeAB0&?n$nqx3?(CtL}_kV{yIXrxUaqM$t!xR%fzRC)7NbHJl*ESp1F-Wcf%g}Kh9nL{QRB8A$sw} zpVK}Soh`K8yz%%(jdgow-O8HT+00uY7pQCa=4KkV(DKvn{%No7nMnQ=>N_boXX>(y z8M{ifri(t`yT9{+N$|X1%g@NXobp(6w^dAZz;@Z$_I+0^X7(R8hJjBrXa4j#^N(}J zUct7%%c-}p0ZS;N2r+-v0-a|uD+F{*bDEo5T(YvhrX+LdjqftY{B*taCb>+#JZIXO zty7jM`b_zH&E)agst`@@_e(aNvYG$Ye6H%s%_@KR4UU%X3p;o<;hm#)rDsld($_%y zlJ)EVT)q5eMpA0bytsxZJKmoC=w}$Os2;YhEYIlr+Ol`X8(u|hJ-#NoR@+nh!j(t0 zW|H4}b{j=18|TLPN1uK!aBt}bQ)#Myi+4e?D;uFV- zJ^P!lD*9?nKW%>5((TS4xfQRnT94g5s@q@rRW-I|@ss!d*KLlUyt#)F-xg-c2SS;P z@ty-Gw{-RL^`F?)qidIotA6s7Ut>4PYPpuuUAl0>+}iTC*rx@FMTv6S@SFH(Y zpUyUo>rT+ER@UV*k!H6u!~OOZ3oR4l?cvrs+cLfN+kVaG`%R1E)}FmJ=c{helO?xn z3|BvFsTK)LI-1zi!1|xzvAbxc?3v!>!AmW6F8#*#@#eOF4gVQ-u3>5l-M=N|^Cv4A z|GQ<`#TVDq$G`q{rdeZsf9k}0r`Ii7^<~?2vzW5;+Zb+FH{ScqKKXahuIX0K-@QNn zV{Jeso2^=0?=^#uf3NP$-56mR`bXx~Y=s-9l}0AdR-e!-+%>~!<8G_%+ia5Z9?ad; zDCZ)cox-^B-IVW3?s3mrFS_CQ=C!svEH(3u6{Z%;Tivbx&yXCL8ftn;d)0!?o!5^% zUOKJmP|WcMC)ZUa`*|&&oK=**Z+e=gtl^A4w(x&X-nUNwWAcOX-?KlZ|MVRAcUnJ8 z{m;N_$56j>zifT?1m<^sKO*Z7`!~qH%Re6f=n2E`$@|3OKU{vm{(JVv>pylf*o~Sn z8V;lBVKiS1r}BYO!aRQOqYp9nZ-&+-&#kC``AfU%PMw~qoZ6JFH|}H~jak>8oSkuI zHjjAU%zdj5dd! zXFtocSnesSnD|!1nD1IvFZ5pTN=dU?E_Hd@uYP&oJ()VOk#ogzRUZ3f{YUuj{VmBj^e4^Ypm360lv!Z4ihfj11Kl?XM)~pWk8xO54k*DPUOV<%B;@*%(rYs%-i#w!CQWvIDXjr z(Rsy#H&7aMs2sKmmRTA26v%1WwOG2JnV?L@{~1(EX1`g=V-(B3?epnCRn=EB)*U^zb83~D z?s_}FHuW=6-{hK?#a%6adbl@D&;QUL?_=MUz4)R&fBh zds=+;YRC1N=S*X#J+!nFUT^X*RPB=M6z$z<%WvJier3Uf%Tr^ombu)JojfPw(R!JR z{MA||%f(Ih?Z0^I)Zz>B-%T3=TvvSi_K3~Wx%2nBvw2n46LNSP)B7KN{rF~+_02N# zZTmWIJv%+&Vc7xkKd4baMIP(QJJp+`GHhIXZf8EZXt>JqrkZKnt3o{c-p)p7r$yF6chGEjKIX zZ+GRo)5&-B8QRw*?{)inSnK4Il-AihGLGi(Ex0NbXj1zsKI5d_8OCX6%GHh={py&> zraB=gRmAxB<>vCfTL*Tpn0d!)$NMVPso9?rJ>JDqW&{<*z`_*C$#sk|J#xn^|qU>w(?7ZQo`yUmtJEmpOE>t(OiBa zm;3VMkE($Y`#o<=_UM-G{K@Q4x+(uH^UjK`JIlo7E!o!>@rLjC8t3rMbZ){EJj$udC?WEZ3nn|ip zgWm~z7IMrNnEF#@$FfD{Q9(Yvwu_ZM$@^Pudo?SyH&`s<`=qsI_RPk8j?d>tPuTi9 z>ey_vwJTMNWGAdH-KGAr?=Ff@KT((8{8VI8=Y$)( zLyCIWuXvTcVaKu1821a#Pv>4NwRdO7vo|@t!E;$Joztlf*6qTA47{|Yv z__H?t;PV%DkGiTiz0tjUwoy?#a7D)?x7wN(*UP7$+6R;$Q(dm~R&aHH54(

dx+% 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

E2tP#6_8HQI1O!+<4m$_Z7U;H5GSyv(NA4 z%N=Q&iA^V0oOtoo%#+*vmf#1SbzRREuHC4XmaMmI#W`NB*vW6Db=T!KDtZ-etIpNo zobI_$Uu$RU+)sgbCM|tau6xlUZ)tazd90y$z}mt&hu*79E>*f1G?n$$&%(Dsmn6M4 z3%MOvTuDiB;EwyxAoyu}PK{&QwA_9n%YM($@Q!UI*WA{K-ui3wZe!t_iE;BUecY}y z=gTIIeSIm%pQOL}JAt|Dltt{6^Os`+*B;u|V3A{Y%86sQ^|rmw-K^RelQhv%ucq^$4Ubl6XSm3(2!l1A4b5fkS# zT0Hrl>T=~wvHFItt*V}y0y<@Uv3C2CV;3ohRBlKy`|R~uck5EuCq@h@91IMuf7=)8 z8`}kbJYRSCq*g*;rcZKFz@5iM*~{#6>;9ctx^(s0iIGWHmOVU>we;3j-7mW?x4jB; znd~`nW}^m!#QTZI^1L(eyv~g&l+Vucsb;+UGj-n46JP(mIB1kJ%WU_}ncRXiPHmRW zPHca&$$LeGQ!d{$+i`N9cj5L4)599rwWS*@tmpWjw76P1=V^GQT=LX&w=31& zS9b6|yR`k!omF~O*Y35)rF>11oI10&COzqZ=WJcazK*>Qr-$?K>lC{eE1RAvoioq1 zh@ZiRJtQT-m?fz2x6m2WtN)&yd?_{QtkAcuOSeVlOYtcjo@A?+W*>HcVZ=!iaP#AEWmC1ww|0IPGV5Er?8x_JPT$Jszv_M1ks2vx+Mn~Y^ulUAGr7In4t>(n z_V9nI6Ta!~(v&T`C4c$~uPEJjaj8N`<#q|PeYI^LyVe`u4cJ`s*t^J{_rBz{%;I*J z#mRcz4&MUR$~3!^Z`}U4D7jEs%;`?lQIDdeo1K!jzwQ;BP2ld|y2bX{p(p<0<~}@q zt2WJd+`mJ1@4}h!M!xerqSd*&yTe6&o~4?K?Rd4+-F*K0+2wn4ervv&KZQ{$(_;Fm zxxtg){z-QH#2x+cmvQNvJG--Q?40^qJ7#g=Yn%QguP=X|&f$8wXBL}g$(h$o=Gk|5 zc70lX<+-!-OQ)a&&p4?UbJ~0E6kNA5Ry&zh%x%NCXA)PDSB9{*a@4^sr}H;)eZSvo z<+FX(Mi=jrMV^z+Zr#(Wsp4s*#NF4;f57^yARUim4{YUUc2l z{LKWq;jAg+OyQDq0sSYeR)npzlB;zWu@0HbPEkFKUX#z9*Qg?8gVU$bb(KY>n`UhJ z463uIY~StScqabI`4jISXy^Ppkl*^)`_IDUmlu96ahLhe@JoJmUGB+WN6v~LIZ(L? z*Xkim>jG~++jC%BpQSqg&4@VN`HzZK4$Rb-I+KxYgi^?ZNXo|J}x~}=E8YK z@mW}gso5h=Pa~sO3@f#>vsb=;aOH|aYiMYskPX+ZQ#aN)u3z6QxWrIML?Uo@mWjv2 zh+GZOk|{+_B_$;V_FI_NQ%wZh8W7Mx>uLBvYyOGcafS-C7vyJEjaeCx-K=x1pb z$2H;-uYnGg_=#`0b%`e%!Qob!xyb8g1&YOg8h^6>!FAhygY?6SOaB?53ulkp|7(AB ze|AybMq}5H268#LmRC?MIEgf*u3PQ6Tb^h`<}^R+5KncPed!Y9@+|iJpVs~^Vj^dJMAN8F8VQp-^_u5;oXK0 zQ%}E1`Vsk~;8;G#sb@;|Y!xd`sUAz(^7y28ls$)2e8QcLxrb9P`DfZLnfS$Clfg*v z-L3O4nT`H4u&?^1!BD+z^Dl=p`wza_*To?BcFn&D#`+(s)-P&c-<@s0gy-}hxz#Tg zFplaN4Xe>qGMYcAP%<#D*?fQQE&KdO?F*lrSiX5ll!f5x8yeeI^mX#7+O$1YZdGKJ za%m~oHs@8n>QcKze)8EXylxG`pxKH@*lfk^I_K}9Y;0Zp&h{(d_<_R@NAGuHc$|C#$|dDIQxrd=w6 z)tYs?>MVJ(HFr&(*8Xu{NSxMu!#eTDu9H5efSM{D(5A|=n=>BE%&csWjf~nZm;7wa z+vtYS2GIo82*xl5f(+v;Dw}CpmA?4U`TMG!w0uUt@V=XU=qVfTwQ-D}BzKKjGrS@aX0Gj87J- z>HRTR+L=WRBoNgx==cFj$ z+u&cf_WR`$`CE*u?dODRHApowsD;h_>$CA^W}QVFSd0J&b*bYuAA4X@bU2{^Vf?XPdK*eKSRr$FJ>?01NMZf)pCE>FQqfRGuS7uwNT{z z!m6jASa((T9G2WAGgI=5JZn*2`}AOW^`yM^_rc7wHeNXp+P92pO{sxs^2&zQvOcU4 zuM)J52Q!9Q&EO(R@_fT2UOAlpw$-ay1X|REyfb=o>hUY*XYiJx{Bje6lTf=_lKVu`Smghkpyu`a0EQHCLvjUf>kxhHsWV zTh(rHEk2f(GHw2ZSF=?Vejkx)IP`w+&KnVIb3UZb+`KI<+_52hZvCF0wNuk7`}~uR z`q}m-msIDgPfg9uPS(swGw~?6VQg&tYF*2ei7QU@@SM+(EBbhO;+>4@85M59g}sG4 zMQ@5Z%V)klMQBX^TCbwWGa)tTYZZNPOQX z_2Iz&Q-!;hf2+HEJdbtGj91RW zsd^@=C2v<=+5YB$qnD*Lr?!{O?wZjv$u9C8s0aC)D-$ZJWo5{75V@gT-YzA zv%NFe=T5j_&C%D>Pl&yIXUvm(TrDo~TDb1<8F&X5j?jN_fpJNB({>I^%agK(r(>S` z{+RhGzJ15m`i^p)!ktx{10LR5^;>k~uI9;C&QG0XZ*8`C>vq?xd%WG0_T3VjCUk|Z zSM#k@59202&EviYkIfS-TF!G{d?J zt5vgBy`8>MEh6&f4WGgp6SN<=q~`7UyxKX}tU0J>l3M~t>+!Dc^|E@|*NcmjLr(mj zR-=A??J5hywd<2Sver+NX*%{~ckIMZ`}G#jJ1loIVTW$Fy0}P^lh~yto3AgeQ}d-fJCe?Z6voF?+`IYjXl0p0hvuHh0G3du$T&w%eZBpWwO8 zeNtFR?%~~?`;P26WuTqs5NwilLh{kIb!TJy12b0aJz}|yqoOy@H%lv;xk_JTZU3QM z-G>Lae##4b9kDyP^TWm(u1Ru}4+Oob`0eDfr(L>t`PXZL(xO`zbj_Id)4DXHZCc7! z#*VdksNVD=r?bu&L1O&3ak=-u(RbXNwMPT(oF< z=kc}2H{Nv*TqS+3kZs{HwTH$@M>6s@WT^LC@C=Za`@j^lnvea%qLPRe{!<+sOIq{cCUJ~qk7JI85`RzW0+30jEvhLY(t2tnfI@pmY;?= z=c3%J-u>abeVY0k?cc@i{8KCumiH}JUAH=7lCbP&^ViE^Rs9y*i~79&Gxl?TTy(l( zs>#`-h2m%8cGfiJ&3kNc>`qbYGuhOlJC*&DH|8!}`hg%KIDEZvM@@;{%-2^M4t~pgaCzsNJ>p-@Tej_eW-RqA zPwfa#bo#qfXCrN2+;$F^i=UBtQ*NJ*V{a?lZRzGI_SI}Q+h^@6^*h$`TH9;pl*cLT z^6P4BYBEi}?LNHKY{kc%$8r0wTYVN^ak!wyUFO4?>xEi{Ys%V~L$8K@kSxx%i&Z|J zsJ3*6cW|H7w0)PR)H*!>QA7$AS~E@- z23-rYde6~oQTw2UXb*~(I)sB!?Q#>P=vMpv-_$O22+j;08YvK0pwPtO3U*9cR z-agmoMSIM~n>TIBH`UH8RD7aw)$ruayKkFY9!*)Qx!JSyh3f^5j|c6!e3X0k$?j}# z@B4G&<2&!yd)p`G{k(ameZx(OT;FH5J2&l)nffXvX#d0~m$ly*e%_?-Ht)=p6KT=9 zCzE%Ua+oxxeza$cUNil$ciNAbqS=$TM_=}H+wEnsbDMU-_YHrqFa9tm@8_Jw!55!Q z`?P7!q~!^BvQ}6481rSC#&w#;Cd}5^(q_Ft`P-Eb`5(2PE|)8|(=E0>`s}#v<*b7# z$unx*);umVUi;qo&$M;YxeuoQnS5&IsnnEeX^BOfTBS;RStLVG?wQe46mr-^P>1K; z$LOGxD(;?N%&t#&x`yRW%$#ezZfoqacz1Vo$yFy~um4yYr*ZX;PF?ESH?zv#Bwtm$ z+_!U*?^cb|d&1{1*5gX<)DvgC#`*s0zF&FGza31|KX*M1U=C1VIdyqI=)@=e;{uQ~ z&~Lq)@~}pzr=ITkEU%t)hv5l!Kk$6-RJnL+1JfFY)eKq( zxQJnFz63cAX7Wn#IQG}}6tC>HS6Xb2{`2|M`;Tec`8RAoV!!O){sz37^eK}Y7KJ&%(J#+fcog!h9=jdnFgpReN2xE0wfAmS{?+LC)1@CzKo2I$ zM-<@Ybn(6KCo&YI5!gLy1#^78s%oqtjBGSEKqwJ-3N@#n3e zb1_?=eVgriI$p}druz5vuk&TvOsD=)dA_J7e+42xY2wIFpJZga3x8SQTJda0%=jVn z9I$Khg_`T$E3zBfPqtUCht`|5`fq>B`?a4{vUq;~B5(BPJpXHI8HZFqpFMzM@6uPB zSIV1Szbk!rGxK=(O}*tea@n?)=r`4JF!puD3!jy)@7%s&)z)&YwAZQTW?tWH_$;4a z`w?yWb<+LT%#77l5!>9p*5-7qTl!rp_uTv4;X&Wj80Ov(t8CBZF_yYf^;>Dp>4G(f zmgzn-op^cwOVMjzivw>bg_QGZMAdPHwJy~&c@)yRRNHHkL+AniNi|yzG@BJoSRTx! zGBYce&1Ji=Z!Mnx2N6M=Fnt;e#M<)eVci8!}pg>zpJXf zW!ddO^;JC`jP(s7*V3oB9V+BmY&1K%x|m<&_&X!bP>H=E-FGIPvo+xq(|1_7wEI6p z|;6OV$QmV7GwGjE;g88#is z^(Qoz)n8ksr(~`$QOS7T$>~8$=EpT~bvYlEZC|3vZ{=h3_}G=QX}{kIdjy^3*>qFt zUS-O>PA`wM?iV^3^GoH&$F7eyrS)mL4-BtT^l1>@`_O zt{QF4-_7Z_#oPG3M3juaQU_xZ0|QIwo@Z_|*WNjsVa0rIo8PJKLT~Ts*R$R*tDnm( zozneWS%kf;EHaR_VxQQph$++W<%MjTbfm~!?4tU@8A2;?7AllUV4Ko4DtQl@()E5k z0a`uYgIAArpzEHW8SJ@tIb&z`V^8+}`l5fEL5<-3Epxv3_kQI*9Q;iG(4UM=_f&O_ z3u9bCdl?j>m+v@ZxE(Yy9T~G-&bfJGp1nIej-z4HiRtOeyGLtw`y2i`{7%*M?h}P) z>Yvh|@^f=< zfpzTVj5@7^-Ez}d`40Gr&P}PlwWzaP-+JydA-Rd>F}r)UvclO;K96)!-jaNobISax zlh(I9GGp$=1FuG-o24 zNfgh?pjld>Hp_%oyDV_z+1Uk$SH!-bKQA=I-z&|$=u~{q#g$piO%IQ~JfU0eu4%$jJ9V4c zu{DRjPGjG+^`7%Sjl7j*=PdrE+zSq#oFw+?lTW?GiA`Q?s+Wwl_T}7|`^xjWlzOa+ zRD-~OhUV(c+V_90?~9SS^jocLL2tu@YnJDg%$9mD4c?IRD*K!2=~Gvt6mM(v8T|cx zNzL8QzwVNptBm@k%PK4Rrzh}sDINFmxVUKP@q0dLl6}*c+C7=6w{+U2o0e+cr-CoZ zN2;&7w%AnHEiL`Z0SV^LP1|I@+<9d7`q{L{kKE0>lb+^e&tXr#zO(D2%)Uv~=Ue$bVo_r@)RWI?I^h#^3eD3$vi}yS_xpi0O3)Tfs*xq-y z=I0o9NUm2cU$*wjti>M5JGWJDE!285UFW{_Y=aYe>Vc1TJe^nCmN-+lO*=d~)aUQi zHQy3ktIm0!-Ow&DWeubE!$0@Wnubk&9y2G|^z2U7?vLN)4sEKl&aK_t_GHIB?~=KT zT9eN_+XqLyq204~3!~cR z9o6;u#lMp>cHMJ$b2fa1_K%b6K6l^JE!Wz#TX}Y=zU|4?QgPaoZ-m|0+iY#Bqkh}6 zP&(vhj?rb_wNt)bO9;3jcIeIywap*xE?<{!HaY*auxQb%u=%%U`pn4BmHM^jw5$Dw zSC8UT1Qm}Yy}Qk~H|nxb(c`68w|Tss_|2n(%Q?GfyV0>P;_b`!?YR9jZ2Qy`XCwQb zTCc7Tc|5K1*ojp~KITUE-AhsJymmSHr_7S{XkoQo{nEGNvzKfSyEK2|-ATW1G&0S}Uxlip`dwb1%#Yr=#o2_%XA(>he zu6EMdXX#b$dv*+5!JITDKi`)XH%N^g?{rcGKC|{50t)@ZG zx3#L;g{pe>xunDeE3olYEKWT8H}nt}pTldL`uRs1T?Wt`m`xlv==v#kfprWxL!X1meg zPr30jmnD-_H^)!B>bkX8E=nkEig(YQMY)%hjnsuw+)@~hv#ncvE9U9md%NB4Pqbd| zoqfl+Fnw3NXTmIJ#kGefa>=cmGpFit;`7TXAu*@U>Tjx9xRP%xljpL1Dq^$qcSTKG zIzdEbUF!Qi!R^s%qD$x57N6p+JXLW=u5+W;Bk{kdJ)SYoaK{?MCbQKSJ{ig^ z?e+{kJAcutqf^a1)u!H^AQU&hH>UIQ&1EZ2f1R&hw(eoeo_V`wPl$+DEb0*danHZ$ z=J87N%C?h0g- zHvYjmyU)6=FvzoeEcrUmuUN2~&&+vC8Cz_3cFZj`U!%vWrOzG)?fYz)=-4ywXS>+X zHH=>^Px^Vh<-z7!?{h-tRPDc|c}V5b;TsZ1G-sam;dyjE=81q-tcVqo@RF>8LgS*`XLsU4 z&*^d-EX_E+>i0QA zjv~&k3jSr!U;5Frw)5w$uje1nIP|&NchS@Jy()6He^389-{&lK>Yq^YMLY8=h~cLx zTW}0N@$ntq@fX)IMvU_XRiy0DT?ywy7brpxetgZpW6JgN3GFlHpPWB&KXe{`-}G;P zq2o{QU$jAopAv+AK6`*;_^IZyWJt%P#S1P?-7onqDC_l2RjW%ACWkv2znQrD^aaK) zIlByd!F5(I-b6b-jO6FBU2*>#wAlGw+YWm8y>KB<@A9(wKP6`Mtm&)(ZnpY&Qs-~5`{ z47NwEza!i{Tb~zetyTA5zVBFaMD(M|*4(AqvNJileyO^e8a+>y^-Ku1GVhh?J#MMI zdnfzu&3A(*pT2X;o&A9H((7L>O;uv#o(t|c`E;XWME8<2RiA}>`4xgLXR3Z%qk6jV za)_I>+9ikRS@!&Q9?L)SmovyIHa)!IUDU^-)P13=%>J@Orx!eVa@|sP>om{qCl{|X zu|9paw~Vx(_-e8Ly8G|jr6*K3SEE||1bQR|fhPi%Tk+Vi-?<(ubeKCu?>uTBqow$aEY za_-yhuRcr9y&2r}WK-I2?K@(+MH_myCqBA*`E1v1mx$||wkvP?-K%)Wh{0p~j=d8d z?$lREv9Nu}>p zXC`hJ6XH`@;xgk%$E7)`S5#HJ4D43k;x8#;_ssW=KizYf@r&zh&#wS?pX_MZe^%I(^rrrdjID`y$J2#sWJsS8A0%j52HMVsur zypxZltp4Qk@Mcfc7mFjugl1ldOI#jwarKr4*1OkVJZ;}L(=GKR&vMs?5h^l9wXtD4 zt{$x}75zP3^kqo$EPi zR$aI5q{FRGoi9FllXLXPC#mq6IVTd2tmV>Iny|>|lO%%?2gA>0&fUp;u{F=6rgD{} zKRna*$us9+b(SR-SVbs<#DY?R}O|O@6>$1)Z$3U%Bx@WZnvdhKE=>nXddOy;i?Al z-G;}5F8{sVmcqE6|3>hR$x~nWOizutom=xf$?FN5^PY)*my2dUsovRIcc5|p)yLk` zmPIUDbmCQ#dvDZA_Wul<9#2+#sOh$eY0>em->pCYIUXtY?q*bTbb{1n<@KkHLOwnU zD|r^Ycdce*<*YaLYH_hl`^0$H<*3-cEStJ^Y@edqZM)VScyQkFQ@_(&-Gfs< zhCTOwDs{UiEvT%_bB%e*p7XNB47aumC>}VqsmEOHu-5yl*?mt}txF0ywKFiHaHePA zhM%fwlbDv?kSV@zZO-@TwB)-p2R`j>U;fju`Mq!N1h-?$AMW@%mCt6+(wh;}c)jD_ z-f5~@*nLg$VBNtd>!J;NU+O7doynkQcjsbdSp7*mjdxMqHFa{P3!mhr7oFOz8Y%j8 zSD6{Fu*6cGQp3dkj9$%_%|+o$cAal3>Fg@ zk2LV9Fc1*@Y6(8m{&}aY&(gOx$6xdBI4N?yeB$)tWR9~Ncz*K~T$uLhjkU@#vE^2i zGhP3h>{_Z>%W-~#tN!UHpSk;V-@W>x_owIb*SS8{GYqaRUiviied(X-Y5(H*>_slj zE0HXnoECOI@xw zX|Atpj98$YY;9?c+pTUsnJ@tl=M1l)Dbt-4*o{zZr7j2C>|qLM_RxOX^y3nC+d+p= zMqZkK5xhQ@RTr|K`!TofORY)M zX1@)LIr>@sy8Z;+%aaz)NY0CMo+Ml2vCihWjK}ZjhR2;U4Wy(Rb|JQ!BORGI##5VgG+%H9NoP|r>2qb74 z)&}~C%llVO12xbMa`WFFe`2AC|c|*Jqh7 z&o22Xd+x3H-IZ#3_vz%5J-hXPdOx*|j|hwQIbLjTul_-Rd9sy1{}VRO!gZg;dIg_- z4|@4ddE2cwg}a~edaU2G&UB`Dt^B?qnVhqlb=G|*i&fT~iTmUIO zp6&RS?wyCH7g#R#*|{`*m0L)`tZ&zz?OpTuap>ZBS+PgoXLUWR*m6f*=R~2l_>76h zvfJkd{h6_BbH8f9{b*IizK@y(vuZU}1vNTd+%KG7G5Kvw_*%C&<(?rIeVw_U&7E7$ z_S*01>OS4I=lZ6`1;>OQwiU6U7S61S&Mc=Buds(6voi&KwJ2c@z;jdMqLfK%9`9=@forXS@p zumUO2IPfZNWlg|h=9N`TKRVG^>fpSGs#0z^0~lK`b(+80&+vT-`z*P3r&-V3`IF=1 z#DCrPvFpzUXg}O>*yvyPt3Sd1aZYt_tBt?jE&ONlV9JKNE8E`*F0{G)%XQ}WBY!8I z?OJpI+i_Pook1%J*@St2qu(!kVZVhrtlnZ>D1#{zL(JdmO=^#XZg(3@HEMkx_>!kY zd7?nC>ophC`Oe0kmA+f$Q(d3EnfX<0uDAF0+3XgTJ2`V|`;Yu*Xqi|kJ%7{BXf=zQ zp}QBI^X{^j<=uCS|LAS@jo%8k2dhT-?6|z>@NC{E9-X?=p19R6erxubUsccK5&wgw z8G(`<>+OWMYevt?DlEx9aPbAd#*@d-9!H%%I%{Wd8CSrhyBa4$O&?`W(rW%a|LbCj zlXp(ucloyQEBD^ePb?@!^kV zRecivo_%xvy4U?5m$NKw{EhkhA+xc?3dcRKQeYmT>&FD;4 z{??s*=Ib||-sR2j-m86Kt#)RbcdPcrWhQ?jWzV!tT6ef3Nc8f;i4#umWcN9Et@6I- zrBaLXU1ffY`?KC?JwD2LrzcfTW26 z&Xanu=LSET=+LD--^`0E~9)>@{QqV+2E z*NW;+Rod;08~qLP`(M94oqs?5KSQxW{jDF5zp`Gkf7k!(oc~R~`l`Bp z_4WT5u6)>c!2bI6Y4`V?|Mx86Kf~4!e;rHue=q*GdGfcw-9oy8CPA|2=c?KSTDzUrjINzq|iUSAVnk`|{V% zzyD|W>(Bj$`EPAy?(aAM8Q!Jw|BmOsBJS~@VVC`DOZ!y$z4r0;`~Ne%s)$dJNAX_W zf?e)^FaBqU1YNrP^4HJ5APa*J{bu;j5FZy^_uKyW%;taB4{k{M7^Y8yN{F*0z zgMVJUU1h*zMFCykVUVq7pcl}oz-Iw-1o0Dq8i&H0N^X5;8Ir-&w_Z@Ejom*$#H9EfZ zX5{0mm(xApDKA{{=KH7lQM)<30_N=%a*5@rym+=E;#RpyR#crxg~6NjC(+xbwjbt* zo0BZH?e>zO(A@2}UR8ZPajP$H{YfjkwJXl=xni_sjra9*S2^wJI?;2U9u`@&W2xFp zkx6TXJl4B5oN~aGU#KRIr8&&NaDrd>6Z@KttED`-%|-ifsw|f3U%7~PmZ;*gJ$wBZ zsc!l?{ZOFz0m1#2$!^y|@{b0__TAShGkKM77&{wr3u zJAT%J@WX{|sei?<{#Z6|Ps`t1-_l?2di*Ezpk~6~mHBs+ohp}q={A1f`CE0isB6PJ z%v}@Q!l&e)Jl^x4p=r+RZ`L;HbM^i%$%vo3v2HJu-s|!=)wApk)Bf=rhi>@;^77%U z<$FFHvz|WT6Vjk=&E)?K4rl%|Z1VVXG+j)v|7O*lv#kHD#{Xbj_X5LYGuwQdGi9+86|db$Y`gaP z{hlpbPU~!$9No9;(OHe$Z5Jk+YMkll*foRc%tM*UkJp_!`Q)M19X2{KNUCGj-S0VZS#kR3Zc04Kb4aOrbvXCptL5=En`Z0ghZa=)ZA+9gG$_0p zDE6e!YD%8a??ZN-M#@G^pFs;7=A7Ov;~xJwTykFJl*cptIxtw-4JBcjH%~ob=J87d| zBVYOSXf$^`cg7sgE4$CjEL(SK$MaM3boK`KzR9)S^dc_FO|^dd-RzkGWy))+Qu5um zG*?VlwYYpvYPP0IyAW&V$L1&Qoqkicd^m8ubuPK|mI(l8U()C%_?M^P0v;t=>{zMcEz8CMmGRTS`ANoKfiAyNloY_8y-b zWifl6^hJ8FHl5D#RV=;lEl=P1g1c2IQy*)VbXW zEc4;ETvHQR$=+Tx+4W=i5#^?9GLCzndhe7CmnoYjyswbY_0^pH*2glhPq^{w%)Z;Q z#|^b(q)n{KL{_XWGFcHkFG@NyWwJR3NAC{vvgMcd{ahaHd-LW|KfTER3~jM;^A0Jl zTrEA@XK`k_%*=|9HBy_F%eCgFgm0U0qbySO*{sYJx_Zvt&Z@T!)c6j5G<|ZedS7{4 z--jO`{xh7gOy%#X)O-+qx*(_be)q@z2)A2?5np!aMy`JP^v6Em{7ptyVPY+Qv)U9x zk4&g-wc2*=@;OFr8(YiQZ9f__@2pM;etqY<_nTV(t;V*3`%U7GTkxgrsxNx|{ju?s zB(ce>uI+lg!XqzR|M0wnW}P~_-^|XuVsVM#*nft7R-Zo3ZoX?Z^Y1k|?w{PJ(*HAD z_lta{^mztT`kQ#673+()^L_aB=-u48C+BVt<`auOt#fI!Nq0xqI_cNdN~%E-Z{94~ zcxS=7Z+TIZ0xv(jC~$PjvfcW--jwlga|oO>{iFE}iTj$l7Pbf1soyJ3o@}8f?zv(6 z>a1H4$0lEvsXA7)Wyd#h;nZB7N$VBVjcZN`b4_OI<6&Q_r!?W`>dm*k7}oF5f8;PP z_|4zTN8jFESgfa1?AgqP{z=h^EDyi&a5rxGlfC7h_QP=Lqy8~j zdmbkws8)6)hwtiEopo=^jTwEe>D#hR#VY$!>ZS)y+AObdy?m3ZZsCp8Z*NX%d08)M zzg^O>dH3dH#gp%39?z&WJR#4`dwh+Jy`GUy)VAj}H}h(X^Y@f4lb?C}z{=y_%C0}= z<>A)eqczF-T8{A7?%j3Ar0zQBe=iE_SjGJK_CHgb8JBK-VmH{V&z-w7_Tgc*Lro>; zoV@Q=eX{$s@~Wik98^%jf(f z0jWsbo1hq3%>n@&_ccgxpFuS@vTe)+T6 z-AlfA^8MeuK9kffK6UM8or$~_SBuYW*W+#3GCNmiw)1)g?%<988U8+dXZD}r+p_KQ zpBvXQ#PR%RIO<;YCeAtUo=rtzLEiOQ>kFIS?lGTmd$P*C8{u+WwrOq&7TnFvYu=V} z(^k#jJmHO3#3gCpS<1)z`8GLz&)w7~wmY_PcdTN- z!sJq*y2aN0lMC~;Z!DF3^Ih(` z^(HfEpFe+|ZI^0SmzZl*>*Kveb%oaSaK%L_Q?^H|^Kh1*=ogGlJ+#iCkSmUR;ik9K zw^`Z;t~EU_P?5dz+2xH>Ql}diSY;n8jQAx9=eZob`Ta&BwIv`r1ONDZB5$$VRVkzeF~#-&oS#z8!c8C#F{UUQ#S;gw%6 zdS`lTW!s*^Uvqn#zTR8A_jaiNiMuyq%e*@h-{sikTllex&dP}kbD!YfFL_;SsmP)g zg~xnz_uT5>3eK*$QIsF?%IaR`nGdV)dVF~2BIB)eVtbq8j;;5s_i2AU=hL-h?{?*n zr6+`oI)2Ewzv(RGI&_Y=<%R8??K7HAFUeq5kU8{G_VACZYj*senDy6x!_MGsJ8Sd3 zU600Ut`U@)Rd!)|u_Lo~>^sLfDLT`P*A{r`B%STt7IrOmziZ|bNrQ*NT^Zc0%l-tI zuQk`*@}Gg{=YNJnk$>!N%$c_MZ0?Th8wzC~c75Xad-Hm2b;Q+o@$1x|e2Ms`oZ68i z)umpmX}H9>veU!ln~ZF<7f3)4L z(*7$R@##^uq?&hz{m%akQA%s(p83M>LX~lo#|bxk!z#P4KDqL~{9^is$?;FpE^gM{ zveantO1ABuT8Zyg8mX?{lzP+nt>8otiSG|5r^dEDfAeit;hLA0R%(lj{Z57KWZ#^d zE&et>?d$fUbze8N##ndbn!jUTdVWgy+E4BmuYvX})ou9Q>04fz@+p66^)v3r6IO3a z-Z^{zgr1r5FE)R@_2(+nKItA~{TKVzA2s>sgE>0rAjFJfN6e+G5g=YK?=$8Ws(*yd&VTVj>S24${0>V=sXIA~7^w`-q@luPgYi7&jgXPv3BM%a`_d`YY<2{xE;j46>Q{y#I1+ z^5j^fS@?^%mDUT;Ua@AO;`Q1vBBSIqWW7k9dHnrAA z+a7-ue>^R9{SVvIviaR!H?6d5J{Bj0y-wd2cQUgur_e{tkH7u4aqF8`cl$F=2i)(w zTD)%8?N_zg!5S4)*e^b6-DIX$eYC#%?M_SfP~rHTn)OGt55G3MQ@A$hm~Ck1vzc<@ z$%Ru)vx3f<_~b5I8#`g?hEKCKTBe$4DM+5!S_ z*94c;`2P_pt)8b`)ATIwX8jDU)0=#Y_ikIa;@rIJI}XoC>6-QC({7n-QP0`qWcDn2 zd-PDsvmLL4ZmTXccD=J}1Iw*LrF{JM+y9k!3x1qGuo3T>bge^ZbelIrms! z^GSbdPWt#<@Ug!9x-IvXoqhRXZ>E%T*1hWEDY-t%<|lQN-?;BpeUWqQi&fRPhc=$3 zl|Bu>{=RiRraU3^$Lg2|)?v@99-76e&h|@F-MuN=V!EZ-!{f3?4V_lp+{wF#>sEx> zwVQK7M6Rc9ikb*xj{NLd@%$_`Ew6(?9%M%yu@eXXToQ zGpEkE^NP=Kx+O11VbSHDyLM;py_mE(a^r=LUkw`eyf4b-`2So!x5wnCdeXJrp5kib z6P40O-j$}9Tv+*8&hzbS4uN}jrGo2Hcy*7dzl-t?S#?_I&!u#_#MW)k ze08#C!_sxq?=9w?PuwT)%Rsfi*8;}Pkw8= z?3?#`lRK}wp3hr1cUnl8!PiBPR^I!jHLG&zq?pu94>9Y$TmFG>Z}I*-^`2c`=j#1ySC@vpYVGU`}UvPq9?dFW8E6XH*ss>zi+lBp4dCL?U6)VxpiIEAAaSTYp+|LO`dn-wO?Z5j_lp~(bj2s z{$YwUSFOGjp)Ol#OXHmbCx}iv{-5Fd?26m}8E(zGQ-6+a3!_){{W+(Fu1J_$RU~i8e`%-} zlF?W5DctDxk2=>(zRg-EKlZMj;COli?`0{S=d-33ccjhOb;~DP>}2ljLc!ZXp1I!| zdS~5gdOWM<{P$zk(>o_~Jr7Tx<9+x?dfOWv*JC+`dpDla(RNLp8mYabwMATZ&50)u z^KZE>FN)@~XqDMEWvZs$k~5r7AKX2CPw!fV{>OIxEw`nfM<(y7e7rSm&pn=}maci0 z%0cN-o0h(k`L)@sF;mHOt?Z^e?YL=eOH5`8-JW#2cWK%yYlnpI-g`RjK6g+0bSr4u zkz0AL{ymQ-Y>~d0UbHP{$NFzFUNP?VvQO4@x_BvtPw6-y$#{SJlw0!i_I)p(5b(0Wvg8!keqVS0LPhdslNt{newBrN{nG)+;q~5=mjp z+$nhaozm-n4u#_V(sKJYXTP8ObyLcE=Z818Y4^T4GoNX8-v!^zTknRwvDGcJdd_|9 zRmkJKCpin|E zwnRD*|1D(dmTMN8`8+AidELss%Q*%vlbkjEonA1k_!Dq# z@%Q~QGb2}=_4@F$B`a{|tSQ;oBfe*x`SUF6^-~5{KewXC6|e3(K1e$-d8k8aO% zzQJveek)H8m$+v+Df0E6>Zp&`nfA3eWS4EO_`Jbu@64@h^`zIX->|h;Q9#zj;<9(a z-wr|fi95u2m;Fh%5#1URFVOq_tnYmOf_F8`%?_KNyKpUTd4( z4Pga8rNs{EEc2bYYQoN(@TERxEoMvC@SYk5tw~PTW4622h->(n7W`-M$ycpi#+h&R z(l|V6@tf_cn(o;*PJL4NwY2W^a=(L`S>mKKr+7ufqCH>%SzP5y4}Y^(-B&4gUVCEc zTmBPu8-90bt~;HXYLxsUYPT z{#zbX->c(gVV@QKUE{*K%)d%!e%1V)bdC$_>@jQSQ~pnd_v#P&mHw8UXMApY-6a$K zb2s+KHHw$+{$_hte#Yi{_L-}0)rHz^tVw*e`(8z|?CBGge0&ZJ45!01*SvhagLl31 z?c80u@-1&;CdFm%D9+sY^z4cCub-WqDlT)#@Yf0NEZ;+K79VVzep7XRnoc;!>?pB^ za*JQxIX`pa^}lY<)F#%)T>CO7<8kJXi-o@%*ti}mHYxXNZ(NtJ8z_8+a?{Tq3g>%6v_sKun}ow&U^_gHP; z->u)oF7>AB6|E2XDjPm)|A9Lnm&RV)xvgyOZNc*D*O3CACs+7CnUkz8WqB+{s9z*) zM-2OUTmDNXcJ6=EzMPNQe5R&7@7LzZ7B|1|{S)06*i^3jr)kPV$wId~507&eoZ|0v z*{CNV!N4G|@}J>?vHhD#_J$E}H-4$8-uh;)WPH}0Uom^za+fc$FRXR`BmO{squ0bI z{Wf*8Udzul`nSK}`~GF~H}}Q=IobR}chL);h_hYZHU|o7m$s)^eNIjMajw4c>DBeY zPc70?-J>Tx`Y9UH?726-)U{A5Hg}5aO3v`aYuDyo@;H6UC56pmYkgCCE8ol;SEbWq z3WA+4Jm2wkV$9>~LK*5$0u67cSl-{9+W+{b?b>su?pDhG878@x>6nGf9#S>Q2o#(k zoIJ565Tlo9}klzMT1OS0#gB^8`6zJLx&g zZ}+^fbNukwE7x{QOW1Z}lM^40*V_FRd6~QF$kPw6pE<5NE6iu?k!W}O`0W1-VNQ{D zYgHyVc*KuACuhkxOWe1*E_?Hf1mE$aX76OfCz)%Wxbg4Q!Ki6Ly>=pB{pgiXvtp*bFy_jfS{dsTnP+h8ero56)UVs7T1?f{G-BHS zxL)+f^CP^)(m@~leG{U*>`A&d zZMVUcbisAEx8VY0M&`=5k6QC#+7WoCWXXY$J`8Fst2E(>-!s>jHr zCYsIkS)@JIv43tz)Q^&~tvz;!btXxxZi@{{z0$*Vy7Yyw^@+Q#Q!`@kHw5PN+Rwfo z7MfbY=g_IWc=!5cTi5Ph-K%S;a&(GkW3IoLb6R`Ak{yTq*H2*lz4?pYyt`7_3Xkty zI-s%HS9fZ%p~0Q@xadWm7j7jB*z#XkdEX_|@aZz0;PnX?Z7vjl98ycZpM@zMc)7a>4OL!E%FB8iLxI8ZpbZXQh-LTizLW&^7XFnRSoZ z<81E^uM-?skKX5=&%aLWSy;4qk4(_jsOk1w*OX0j%MXlBES%=Hv}VFgku5*jf>W!r z`!Anz&h*}xU@h4m(6ZFSWT}dS$Id%N7r#aSn>^+7)@|166K34ozTt{Vb>y+9)yvmi zn>JnhMEb)w7BgLWUM-2WSjpy<>L+w6#N|HOvU9oSj?Jsf{$=)G zm$gkxFO9ZauJ5Wjc}YmIQBT~lu5Z_RH`SgyqFE&0b-!xgDei6S7I^OUT~?Ydw{Cu*;*yv}=EBrPFp z{)P{`ICo3y-kzO*@5Ixe>pLfH^6r+YwzzH@bw2UKdD|NopZVtV-Y&mgcHHdv$A}N9 zHZ!IbKZ$?Tu_yPduAlr8!5tHfIUhcJ*zdUHk5=h%hMl#}9eMsGQzA|8D;D~FOSvO; zU+>0kw)2_ylT9y0gqEC~a%1b$$%Yb>T21oTaoSDt3iDJjQr>8Bd!gC0X~lij?tu?x zv%Br=3rR{}WAuw7{KqVf!XFbK1{_Y>cT&h!_Ljv*sV|niD~*zMx2)YRv}IBUxA;+g z-aL*ERnL`9R;)ZGo4oAPoOR5i;x}`7OJ3XT%2i1<*_I(V^~p8ynLIL;+I^S$_SsBZ zxYB3a;*7Ifq#QOzu5WzavhCQJPjB1j6u7&l>|9ql`F>hun|rE%-qYTH!Be8VcAwCD zzE)g!+sbX*F2~kh_6nL@c-Tnk6~pcb_tyFC`?g(-F7COZF0wUGcVYRFTeD^NuE|P_ z+SvPi@`^d@s^KCt<7Ed9WPIvX zp7<~)d-vYLq)xl%ZwyvkcpQHGc&tmMj<*k+;*rcP-(SE-&ZH}{&f=^{31?p576b-BFGtYS7RiXx|UJX`kr z+^K6`OeeF>bDrnpRXVmIJ-FbzZ$f|O-Wk#TF;N#TdiGCDzHKTXy*gjNSiPuvr|8pe z)3b_gmy@-kdAGDI)ll-PVw`Y+f_AMemW6E$!r@q_sO`GmO)u>)?R=9mQ+I*=+j!U% zwft=PfZbQ!-++er3{&^+oiuOOi-|F=_DZ!yKbh{EZaDpM>BdSv{}rzn#viFHl?nJA zbi=BZuY%krBXV=|5A%~#`r_ZYK6i;GO}{Mm&)?yE*!}7gE5B-}%}T6&bS0}U*!}wU zr^YYbYK12G$L#hv_xzh5({8I4 z>=B%RJxt}6+aX=v`3t-a{^^vH`vTw3(4V07cE-E3=TDUNTz_fC?T(+N5PrDG;LZ9) zQdOV$zh~d92d!~i+Ud^z*1PVhl=xZsC6@F3r*Dcp7!twvuAgK6-r$EDzjb`Hek0z@ zV5GpX(PrU4KkM^9G|%l%-h8a`()(L(tv_#zzrLXKZhPJFor^z< z+wki1JjpvN`4tbIU|^6GJH2uD-nR#1Ijy6voj&!h;%jAb&l{Pbxl_-bNH{*3_eD8;Hka6^Sd)G@3*9KI+-ud z3=8gRFD*Xn7kl^Qj%5q2DzkFR6)*o$e`@BwS@TU{)!!|9s>QBvkrWAkox6TrmrB>= zO`ZOhyr(j^?ljeQy3(SVbA8A2Q;J&hHn&&2yz}_lyU@#(NniJ@=~ax|KfUbCl()CG zA6b4-d*YgCFI|(_Ug3-7bJhCSIbSlZn_p%6`P{~HGrn&qH2vP++nB6mw7u-_JI?6$ z%4wONYR_inY3_)a^LI_K;TbcV%b&k6Fi>mh4)#PtMNw?$88a|tefa*nuJCij^<(=a z)UAK})Ia^taK!BK#pD;ej_*41&B$k3Kz-4_4X%;aGhc{x?y_50{%RknbCWCg~)cKY99B*2v9syq)&tO>B7L+eN4ARxW$|>W6sAo28#;Z_K=0^>MwFvF^I! zU36!X&m0Ef$gJc}1Pq;E~Ns3pv-(t(7C)+}&mf5b( z`d}Pf`d3`t@yff*pPZMBKBwN)_-6V+{nn+mTrWbzu+NG>x9|M2x(T}B7`l>dneV6m znu9fs;W?mH@`{p0`WL;v-uiQqX`l4O_S9N|%h$2=E%#;oRXp>nW-l%JmTL`sCvPY% z-M#(RbEAESb(d_qlesBVOJUN`x)Lp|fs313|#whgd%1NKLTNbA)zY@AyX&-p%^5*BFlBdqR zSm$cB;%>8_xYE2U%f%0U4_fo<8{f5?)o~@(vQy_gv79sOQN?P(LS1#X6-$JI6<%vS z+Ag(Ac#=?)>W;R~qZ3O^B`ev(^^2D4e45+&xab_8o#^T*kINoEk6HH4^5o8wdPknR z?we}8bMw5Pm**R;eO5ee`Sca{Hpjhul(YdgG%lanz!B&(`CzM$s~-G;@4c&dpkGF8fw)hp+8H|LpL@$@(Avc%E%k z`x;=~8Q!k9$tGl9R@d*gCItoY#%HBFjeS{9?YjGT_cnFKV%g|N@~3qTxzlrA8iY@d zN&0o{x9qVO0i`8xT#j#C!E&Z_*WJ`?odX@)r%elRsrBIrdb`f4usrEU*N=&g&-%OD zRV^YrUisc$YSPzyRjPZ2xs?`98ft-QPQSfTH>!k9v5O6a>1=Ipi1dHKxhmu^4JUTXwrJBY_0K5cwk+WgS# zj>!KEyQY20eK_yK>7b{*&#IkLuX)OS&PbUxZ`swJZu{9%-jS?#Ja76OIk7p~Un%`My5g6ZhNQB?&&*r=rBRu{#83Bp7MTcyY9<*xs4JVl^@KGSFC1DTRUfe@v})>v0D#)a^Bn=*OaBD zymZPe1_687kBrayoFA+`wx-GMZuqaGpBB&halHNAvZps6ZT+~;s5X9Dk-W||5t${c zkExo3oKKTvn|tW#nP!#S8t-qc-7O-tJM$Gw->qNAEk7)N^1i(;a;2r`Y1`u~cIKX{ zl9T#m9{Oo|#Z#H4btm)6j!93Adj0om`;BL9U;Y+vDExC-*z;DkUt!jp2cBkr%9=U5 z9SY>kUVoIlDpj5L$zAL@cU8J`;_vKUiF+$AT5(4nJzo50(`=htRht>ls^;DOJ2B=~ zs_yK|o3z7~t|x7Hr7UCbp5?uM=B@;u=87}FwgIpR*3#TE;SY z+ajw|k)9h}>|R``y>#H_83u;L)3c=AQ(o|k$aCxL?QNgsc)@1k4czIDS1^KG0*@-353%SO(ApQEn3aY^p|EY+w-L0N_|l|^@Q^Nv*AHJr5V z?02CJs;v1+@8A51|G9l?x^%a~A&b(7XAj(s4&S7)G-X=puW4!z?uyNn`*^C%FEZ%x z>s4D$Y?!Orex$Em?9>ydZe5qAS09BMR&11)dTXDbzT8OWKSOuSxr;IRPyEum6Qs)LqL2UTQQdQ#taqeB(nkMp=K);Je&Jz~o-?N7RMXWdG*l#_3LV8L2D z`DM$M-`;ArW|hBeN-VbQ$Zhm3TzoXR-)7pmo!aXjKm5JhBV~`nbqTj)PeSZmvY%u< zH(hPnS{GkvF26M?_G-^cGl^O=nJ>3FFV3#&bFtL1kGCq;HWy;4UGVYr#V|wDb>~i1 zoIER=dW84vtVcK0);zm+J(cwqeea@P^n{jHBkLv6;zEH=sOOjcW`RcAIg-o6u zDXg}&>rES{=lfYs6L0xvuVidl=a;ef`@{7I&ln%MFZ0~oVB32Ylh3=@Q;O=k3-h|) zJa;M57rb$E8+TV859?k@%S{JL_lD-!-1FWsdB&Frp_jqD(|$h>N&CpC^}T)feF1rq zGpk#Tw%_}3X->dnc0to+KhA%gXZ@!7uKC>ex_kE<+w^VcZJhaLkK>KVrFA>@b{#@R)wg1f|D>Yur=#G^#V0B=*J^m}x@)rMcz1P^ z|8t}JPv4|5p8vC1w%VsM;$wWz#z%MJvzu;fyt(~2`gg|cjMGA5?E&r5Q8Bqkmgeqg z6}qrRr_(b^WU|RhEzhsY`ic|Il)sJcP0c*I5vj>|H+N zZ{l~MS?@lbO1ZD`{fzht*ye`mN`FPx$F4ssp#5;CL|OPnUfW9c-&^18zg~L$$E72S zL7SzDdbywVUy?b${Pazo2h?Aq193VtbylWqU`Y6UV4hE<)$AiIkN2HvNb}lg%c&K7Vx>tK96txn|JiN9pkAS?cNxZI~9BAac6 ztCCKvlcY_Mz%Im#n(CZhd>#o%-GS#cp2Qp+RPz2j7>4$*)#9Ym!>Y#@TbuH}{=yJoj!B%jkHMvs)$CYKNCST&kru$E0!n zb?ZHj6%WtsbIIxFt&F*F$tL{Kx`mtjdw;a%@tm(r_%%BzPx8H`#+n_$$G@gTvg%$g zcyqhuN$$+4PI_lkMAuESo;T&s5&=>3&Tj`EyVW1iv&h>gnmTK9$;aD=%jfPfxfA)- zUu?bM-MNlyzkKW3)f+q`B<#v-OY^nIbxYS7hv~+yOJ(~~wc_u`1+(-nFyB3w=lAjP ze3_*m8ZPXyyDRm4;>&6eU*oN06VDwx=g;z+J*Uje1+DWvN_n=-+q3*ithJxT zl7+T)k_~$DePy*fN{_eCtDf*`bdow(teVpr%Dhn>CU6IOOtM6#HL9qzN4K4F5{ zTLuP+qK}u3s~mmMeW!bQ-|L_5tlz)O?%eJCVukXYfG=WK7VNmed$@X4$l*y_eS1H9 z8QFC^o#lEpRg%H*d3)hz)BE#S=S+PPS{t?Clk?Qw@fj7nUVok_`#x9W^DW!$?DZd~ z_3eCrGF|ZC#7`)is*{@6+5zZ9o5s`+Qq%)^Z7J8 zBwPAar`h}12UV-ZTbF-jQ_S~_TPbFKHDZfxw&C+f?nZUlDz9@U$rP$F9DQ$BslBMo zoU!8ljN28*(rwP&+udARaN6dS$vnl(Wtw0186By8y7s-Xr)1rXtH0jq`7Sc}TW;E} ze0^SU*_A1`H%b|w^PKcNg>eqg_v~yxsfzb$zl*0vt}|R=>aT0TxyH0y{(IQzZLM9e zO>$)Qw-vp9+q?A1^X=~rA1|D<=+Ddirc*Mxb*boj(0KdL$iB-8%EsIO*$;wb6<4JiD=hK|5aaRY}59B zd${zmr^bP!`|T{FmzZ5|S;}|fY)xXZop+IPmGk3y(bY$NxDI$k4n=G&CjM+ier{9hljEv0j_{ zzTuDaEp=&g;REx8zs*)%a&6wZ>>VNNRw%y}Pt{*={)+1Is|u{ikM3HFJ@aS%<8PtA zNPqSXntbaH`=^S$oW02**XvokZfondbF)smS}d4i_vqe>TPL{! z1snVP1?#FmhR>A~_+c(?adoFb@8Xm53br4!m0fG`yZwIny>~U)W^SjhiC0U`?Om&2 z^DVZ);fl$PqqBeVtghIw$U$S?*-PHND;+#<-sX?|5PY0F>)D*i=M2wUuC$za&{nV` zbMDbi>l7Z|I`%&6<96dStBZX@waxZa$(j1jw(d-wkf(HHy5{8R5?6es?wTxg2(6R+ zDQ0{!U+BG}+{OQv;p+Tz+EX2P9U2%|u0DMK?a$+9 z8`t;N82FWcoAICF^nZqq+~bRmUxW^P>nyN;8@H*>qH9yR@da7IH~lMOUoHNo`A5Ge z>2ztXs;yNt*DcMT!mp>F5c|2dEw)#;`$SQ#do0&K<{ZsPtB62uDgumZdM4Deh#`ZspO+qVwrLQ zr1D20nXf=jM1S!*RX_8YJ#?G*O!HH=pIJY##%`89bLUS>>WTfX+Sju`yKw!;bCaF= z#o0g3@!#8Oeefp#jT#R?J2DqL{apN|-T39FZ{8MK-Hcd{dk|BfVt?{@<$nfN%j<8- zD$ZNQ{`R=A-#Y1U6-(^3_cy+q)Fqw$BbT;1^JmDfqaWK>y{~-OQ?9T01nIcypX&b_ zXZ~kMnfSBocy9mV!*|!Gyg2nc?D?gqZ;W$o6@PcXl7FzD;cr(+ze?rfFV`dfyqx~^ zcl+MICV%G?{bzXQBY!Yg+#%^$4ucGveOJ-M4wOuSV;=77Ndp+ zP3olC~Rbmx{G!&Aeh7nf+z!`jo3v%KDE@P4|tjd6F7+XywMKywm0TZm&#z zF6A_BvdNMY+|iXs|Cv0wc1`@Jx|~Uxb9JtnZ|~Zx&z2s@UAyLu+OFl#{5Ncz-+yAJ zSu~fqt28aIyar;NQR-;Y|Zz_(xfjh zzaDV?K)X@$g~w)><`!~A&Cb;{ezT;b;nCM;lP_C!-o7a{Kk&6|!oNUGsrxpot4Mb%NA<6tazM1sw=3!AQs*s_eddnS0_~MVmcKO0 zs(O8^eQDU4v_reRw{DyG)Vauo^W*H=sk3-}ZEl54R#oX~cplbb)w<=Cz~ekckHW1> zrnN3i$&3Q~+?oT23=(+!kv zg+zz>Cl)RbkGys+^GERmpPomvOQ#2HyW`ZWrL1hkP_1}1^m^#g#p~BLvQ6V*(^?pO zxLwXP#%jmK%Y3FeQQx9nJU%ENaf))*a5QT>G0DYKuYtc^^61AodR^Pa4{yAs<1e{9 zHE@;TowP;!+PYR*%`N(FHD-v8I%iS%Y?58P&}`?- zu8!SoG15H0Ow-mKeBgfe`PCcdxslfTX}Z$BPd2??8|5-hc9#FznVjkZDVvlTT;fV! z_NC_BwsFgs&;Aj<+cYG~a_VjVt~ocZPd7H`nXc|SN$v4%dy!ifd&P>g`=5L<=)EC# z&G-28aC_OWJN%6PGo+@l>OXvbNOt@2Ki?O3xIbA9h@kH&>t zj;Mc&3Yy#Z#W%M3SapJ`ad66%XXlLFTAzCQc5yk1e6C;o#QSH`D~26;i94Eivf4$x z)9l?jHTkOX3Js%ljaSzv1YV3>r#&rX%Xh7=GSjJM+)an(_nhuXy`0LnWPYM8!%7Aj zmYiU*_K7#285*wQ*jakDgsZw|m8og5r0iDT;Qrm=;pr7uQf{7Kx3(;JRo~%tYnO#Q z>g7}p{K{nV!cXzxFYWSAwsgDcmPr7k^o&JNI6$PI^GroAR_LfB!S=^6hnP zNGg@Q=ls(9y=ryk^y^oSpU~Z_dOqd(5|83-b-}L~o_%B6a$P!IrkelOtp5z0H)l-g z)bXhoyCJbuEVa*GPe1=+s;%e51Dn;c{O4-FPQm-74xo6p%Jfl=JPQT+q zP|}U7DsB&g9`AS>B4wr3p<8OU@amN?L$Q4eg}%IjYd zdu#nv|A#fnV$ruxdi&jdExIQjeB{)9+hei4kRzv_Pk8VaPqbb9 zR^0E@`Y73>GRt%Sl+^YviN0DRUhlYdrsx;$SNlBI$wzJZ5p5mjXElFSvbO0nB=Z%o z{0(Mwv6{asL8CqRs}HNdtGbm9i)8(;8i+Wr{I!gU6s2D+(r<)M`Ey^TsB}wL(wXyD z?G5?2?pV5K+q7Uc<2SZX?Wg`{m=JpVUh>WE8ybX*1K$!>c0B79lxb~&-KloiY3!lXvyi@rzz^xjVaUOjGeCB zI;r&{ZSrUCZOv<*etNgPbA5t#(b>Z%wpZPnRn$Cpr>3i?+U(x2E3;O;b#0$jSE^E~nnHQfzzTY>fkKX!sO6_Ssi~kJEzrDZO>i#ywzN&9Y z?fJ$38NzpdE1rL+eDVF#&+K1!$KQ0nv*mIBull^d5g~O~oxiVm<$mXv{nzUG>GC_? zyYGAbjQ@4n{!RTmGmp!E`FHo%h82HTwIkVSUwE&-_WBw5Yj5jQ<%=%&*}wd|`)gw0 z?@<1Gi&x4QzxdB^^PUo%O}{yM8wR zy1V|(^5%VFGWnIuBV+&FSJ`#zcHnZu<*H?2lRdxK7qwsbCGo3$X?RegscHY>t0w=h z@4We+;r@JaTee-+MR(So)}LXKGOPY|+3vF1Qz_bF#}_=ZIA_^;b)EOr8AnCkY-65W z5BhUl;nMZvYdd!4D{c$;GS$6ja-mDhr0G{qbe}PO9C{`H)vtMeYo{#QRJ8uiUQ|*O~U+~a!kB&}s#J4-QrrgoF-Mhnb%cP>hu1A?F zcVopK&lO>%WStIiB^kC;I2kyXAO6qq_U7@kkK?6&B5r2ZeUh0U{MDy)>KFIv!C!p}YL~14l_14bDn}5dAA0E|_`^=?oCD}$@)1vy>`;w_uIaW(XO9Cr)g@ao!KvaYrW#hzEzv{e3A*BpIyMUd4gyHD^d4jGdD0W zuwUl#TIkbh$g()bQ^(U&HSJcfk5zk+XF;xYQ`^##kPvoB_dGA8s z>MfhVU19q#uBm&UL65wv>k40Y{aJ_ThdVvi+ArGX+cX*3UfuLdK5k~|PWkmOy+3hc zB#et8SXYE$aRY6I@FWb#a-;nc^49ng#>87y?4YZrVsGs?tP?*5n$oe#-}<%dp8ia` zoi&Y)w$I{w--b_k(jFOC*+2O$&MO+(`vcGN_o-R6vJ1;PHFx$cbUbqAVBxl}yU$v7 zNQvcH2TT64RKF|j>AiLRT3$ujs`8dQrd!%;xWwcS5z;zAtm20gF;FmK+Phg-g`nR0dCJ&yIcrR&9BsBb)-cU4k3!gF)# zjWbgd6IFk1UhY|!a&yw$=ufF$lOD~RsOEf5YEJ3%yOArs0%xvtOG|R_bk#VXEaot6&X@;aodaTtUD|$_w+i)mN`>*>?~OJP3@)2y~6^mk8@ij1^+Xw zUUvA?MEO^zd4Jen=P3UAu=(%piC?|b_Ue1~8m{`Me)`we$=~1IdMN+q<%WN23hw`M z{Pp4BzqcoTE#3J0^vn5oe%6O=vU|Hu@ZY)F{I8Dl{%2Sszn(|_-JSkb(eiut&HQ_Q z`qymbA+BG2nS1X&K{@O9^JTUvootJ(xD+`}!fjD5SLBkOs~38HD|eP2XEQAN6Fqrj z_={=hXa4eeIiY#}qnhI`_b;ybCOP|n(Xq{Ugt^WoAFSKhb6)7U(Xs55U6(&)-#W4K z;T>}}=QDLqpFav3%#ND)CS=LX=@WdT>Y~mc*f?Ke$FaXNVgjq99(PTZj@DZ3oV;bu zk5{|G%vN&ja8{SQdsXamVYtGwMYnF;S}PsRwI!4em(60IlAbQW|EUHx&RcpHdGfG~#pH{UZjD9TmS^t0?B@Q*o)f2p z&nRHPa_TEf-ZS-2flu;3=x*GfbS+uxx&B34@AumGqbIg~75>5>@#pr$uPdce7e5XD ztopGdGwV*}k?nHMli@45`MZw)xo`jPsP<>pzTNwsrthpfc(wY4@pt_j=J}7$zhhop z-}pBDH}?|h!>9Kz{$XEv^Vc1JpGxO1{@#D?_I{lq-V*220g48iVjL~_BO>#wXRluP z&G$^9Ns%K7Sj_S~{y&4?o?yr<#i`n78}-iV#3w8N@R=M?IqQ%91f@NF zmoM-Nm()7FyZW>HI%t}rWXhJar>|&dKogl|LEKSBW)t`bKse$Cd~fX&;JbHG~;LJl-~U%viI~izSZ{~=kKgLxVv7izCc#`4}Z4(jaY?y za+*Y8()Iobi$S*mSSg zXU=E5%olH~WZYnoQ+Hi+>1FBPZM)=Uu2(DHlJLK^vpwSJtQ#*sorz2RrBr1s{CLgD z=`woP6V>t*IKvPBXAt#e++V?cG_K%X&T_uTYyDMnKK{+R>y|iCu`43_-f@Nfhf`kf zs@~aa5Nx~Y!P3Bn#!sfeXLdJ7o+0VTsHrJ+~k~N+7;7;b&qS@8y zr9bB;Tj*_>bJ^&L#wVV0#S;yi8gd<~H|uu#yF_kTvh;9Z2}8i1qL2O`-aXxSxH#zJ z>FKl5Gv6F)HoGLp?>TXmn0sY(ly_`vn{ITq*vdcKM8u+l*IP#fUAV0g+-2nC>%#i` zNx$}v+lT+KY^zuQp0@kq4duzVSG7;bk?9hZ4!d@4q2KmEXU?mu&MRyS)(D>0%{r5R zvS-j)*_IV2_?Wx*Tzgb+TDbbm_O98Vqu)!##GTyLv*bhfwPjI{uK6!Ko^<5o$D0}L z6O-5Z-S&Ffvtr8aGds3joBYZxfjfPBrRIVyy!&=7 zzB{{|`RcUltY@#~uj@RwWYH;|v+l9X;(C?6t?^bza}GMK%2=!y{JQem?c60_O-qfG znM_~gi}v%$`KMd5ZN0GP?1kOxmd7;L7g@)YO*d zHzH-U)O2;{InB+Q*QItiA#Nj;@{FM0vQt)^;L7j5bL&yOsp0Q4^Skym zT$_DtwfMZdX#qX*3Q4KQ^ew+=7S1?XxYM(9;^XJ09Dy5FPL%Zi+xk6i-JxBK-bIU# zIJelS%Y zbbh;DUbaao?CP~ciEP{BYpgitN?PWe3Vq*6m z70#J4apKL8D?4&;MJI3Fw(<3n)X>mlvCE%a6WJ!^zB#B?EvL5VNxA-(Wy^0)N<6rsn(L&|6uocL_FeSw&H1xR zHCjyW@q!z|ox%K3bIk7Mx@=i-Atiy6eUqi*@mkkaAOAkx_}y&J(Jjxy6WgvOOIxLR z$E&lSNPS~DQ6b@}_r&YgMw`yr1*;XSa(;`6=2txwslTCi0jvB{%e_bP+*5XM7QATF zdi8hE=f2>F*WP{G@MfCs8s0*^qD?GrzJDLbYMg8?7uoD|FU|YRmL2>{v%F>p3JBg7 z{W$f*`m^iJ_^i8&kN1gPyR~29)MlNN%eJxBR7^CSBd0xi)9I{i&(8kv>BjDiQ}=z! zyf^Q8!Q?_s6TuaXkI&j}WVXeC;u`^<^8ipU*!C1%f^mpTZ7-O z*zvjB^W@xw6;Iyodg*hrJ(*{^qVDE50ku^Vgpw!UIs0fm_r|Q8^HZgt$7H;zSJ@n^ zXY{SO=8Uw=vYX2$ed&4=*JPENBB`>YXRUUnK+*}bfF&zyCk0A2e*HLm`k#bnapLxg zh1r$admndPiK_OjOmB=ozNK1rr6g*&BQ2Itwp8UV5y5+mu-~3pnM# zwK@3&)@BM%_s8X;)iOr{^^()BylIQPrMp$!U8?=A;;e7eVyz~wT(a`syrTEJ{G1f7m+L{FS%Nd)IvGitbw}46Gd&l-sA~GX@O|5rd0(l&klXlkyYlNG zlhf{}mOt};EO6=Sj?bNWHtp&P3=H8n(pKE6mb3X;^*-u(PmUq2(pV9VU_pq}N+ljBdc$f~?O6zsWorIot7?>?uu z%U)Q$+sqkr@x!J~YY$%XySnmDU~Z_U#d5c6x9kqjI#D9`?MP;hk^G* z8Ft+byS3<6$g`s5CNF9%Y%`>!*bhn*65_d9s+BdUvA(d`! z5s!BY8}KaO>u~qvhLtN4PJVS$ot%E#h#~B^um#xKV1B3woS0^ z&0XpNMfzH|Z+tVa6})x2GeWfN(-KY1nmvrCFXPJJu~auyS>w&Z0ihYGJ#~$B&x#Gs{wsq4E zZeQqeO6mHhvx>g&uB>?B9Xr{(*KA4SWY-Vg{X70M*crwZKfEryHSgW@rJv54&QAE@ zGbdT<@b_!#dgoZ*mcBc6Z01VWZQ+w%pZs`f)o-!;&OIhuwtd`jOUjGkaJyjfkyKHc zp7_@IRyWc86_+=De|J}R^QpYVqN3H@(@*6th?MElU773}J@xh3*PD!E3nQx;e+WE@ zUL{&I>2ybul=%ILjn%bpV!Ffr7|uPe6QQ+ZhrRaC@by+py{VviUzWGH@$J6IAClQ~=XPF7j}Qwz|9XX`nf#XDuH*ePKRmV>Tu6ZpxRd92a@yTc*0k_*LqO*d0xGNvOJ^Ih~*=+xV`&hp% zKD1X}|D)2CUDlehraC@Fq5tNb&CLCK?X|Y&6y5NXGh3hV1|3gxVO?<}m}T1~Pv>=$ z4y@f%xwUQEHIX{e+3_(CB=Wd7nlA5J&s%UuTKkCMuAZXHvWJUy->!;j7ifCxV$sqQ zcWIwCM-jijo8H6CD&i@Orw$%9T<7=u@!$RK`!ipCj;U|4;k$a%Uu=WSHkRy|yvdq( zD~sxO&Ym}~uUgM>^3^pLyH zo?|R>Z_bX76P|`WK9*&)&dK#7(~UU^>*r3tzPYk;?H9c%#|wAO&2N01wIk2*@r}1< zYo7YmuEdHUV8_Z7=7En-&R~-As_1veUJC`4u)08K@e&gLY*ryh?WG9$v;`{{ zU;Ot&`^eg=+5F3oJ&vnLd|7+Aw&T&)GnX%?SsQhmZe6qPgihwG+x>#SZS6i zvc~>pi)?+99Y03 zK?k}YrLGZOUg|9!*Pc0#^Vr^rC(h`r9x>3Uyr!6Hy41AC`^4G>ChKnNFPWt-blW31 zQ^Dx*~WrP@SX zdUmv2YPQne?@KK{_iax2&rq>c`kYk5jyl6X#*g;x`{3|2Z2RF7v-ORgAOAD-gnhTl z-Cy}N9CXTsL`autgge6rbB)79NM zU2;#(e)(hD+z#w_UefZo;?A0;+^U|rldgoW)M7HPsQg>8+V+FE(Z4-0)mLjG&wg`n z&zpVTcy8j-GN-aK@o6!kdE$k)WM`L8c0Xi#TDaJ9>T^Bj6`?^fi5*WCS3a)wHZYq; ze(Mjc6Wa<}@yDUrE6iSHE9?qNIiC1LOE*z{%jI>dsTWVK3ZHmvQtFjiN+zp+B^$k( ze7v;R?`g}&H93=`Q*Z5=fB1EU=kjH9+S^y1vbwkK>S=*l1)FcG&HLTIRewUX{Bf%= zeFcm4e!W%CKb~>>S$%W&pRHal6GY_F_H#es$yVMn$HaY;-NN#$KSy6rhs-i1hr}&} z%|5E{IsI|g!#L0oS83oA3v1yJ1}&ToGjyqsGV?*+mHV+Nwq4C+oB!gA@oC=fS6we& zlkr|VKSWbYugkNtr~hGJaAB5rRr=A>g}d{@mcLaMn?G@tvDJ*TtMUzRX;0ZPy>`or zb#t~ZxRiSDWXI)oOMYwQbb6G!O%+eIohmrp>E#8FY3cW(de8F&|8Sd~WpSS0?whi2 zd3maQpy$$uZ$9qVwmf(vumR)}WX$-o_^Ie?KEF*fA>(B&a}NFUwdwlDTz32o-#&4a+@`4=^fp! z<9XRDUp#tPn4h-v^%SjDEb`e(p1I%eznCK2=2E-Zle2%y+SlK9l}@jo7W#bk{VR(l zcGuVh_JnRVe_pw=VA5-l+X?K79VdH6YR#vh3-BUFeIyK?YtXXfP%Xd6Y-WaaB z)u^4%(lgCz(!3>;-2=Nmi{%FzY`n*BeD_SB=-0!Z-}COC%6<5#B{O#Fa-ljc33KyT zu`!o7%1uAF{VPZ2PVd%Z?@P7AGu<8^&(jSJw3AcSoxQ^GW3%7{1_pW2{-68m|8|8x zOQJFjZ_>&R2OvL~5$>$qCZ-`=`xy?V^=eygu3 z5!bi=d%>5Uu)O?ymFD8DcdXnp8B$aj`~8LFIJA#&hh^qw&0SEnB{V&aJ?l0a8&T^2uFLlZ1tU8Roz^V!+6)2n7Z`VPCCAK&&+ArGTEA) z>2uS6=4kwD6nmArOJYIS>yzD_X}?zm2%nxKC-Z$y*PU6-(`&MxsJ<8Yxpwi!HO=vl ze$QIXx#;GNzRH!GI(u)$X4xKJQzThB!Pv&)os{K-usbIvtxnzZ`0dKPa<>a2??v{Q zeN*E+xtg!2IC)A~&9SFbYRl~dvZXzwZuR?Q_HwSt(-BUdc=6TQSoh4j%_lxf@@#** z_3hM_m6s%y6Sr)+G|i7u)bx3Cy! z(TjxQKPMh6+L^w$f`1ox`aHH6r6($X-&8zI(me5Rr`_Cc+urIK-!6QT3O;JRseJF3 zbB_+MEcGvPznH@Q+^RxjT}GOW@~O*P#Mjwhc|CpC_o-S9b6oc{YlTfUv&`nFRkbMuXtFJ8UA((vJ<(1Wg>uR974i~3!#jsDv3^j+6e&$BwNmhHPX zUHbM-Y~RCkR-1H+Z{?S$TW<3{N{^L-wD zkNPfqw`4}F*y$tN*p{78o4R$`qPtSNm8Ql<&q-Wb*6-7_?dg%}(VEBQaW*b>^7&@Gs_Zg zd9Jp8&H3J^%DB64%EHYI!P3(Yi`PDmS#*2Pt`D!D75%84U*sibc3;)BEr038zdsj! zYYXlS-yiI}b@fJVpLXFb2e(y=X1i2QRyJX`0EiwgcVP8ZGU%!5Le#=Z*!HWub|3wwrBU<#%KFp#D`Ab7P~oN!S9IU zrB|-yuRk$~i9hs*Pto@7ne%SAx4CyX7WzBp-TCx8{r<<(nIF#!K1mZ_RmK+eNhP(C zuVu@wQ-;|#W{cj1XE|Ott$B5={YB4jvH}zI@_u}nxa00W`7JUkCn{Ta8s}`X`+C6q zTl|T)4@ITJO-~!0NzQV*byGAYrgZihujN8Vb=O7|>AHr9ck!~ADsG!yt2@(z<>*I= zBR_V|DafC^09SKG63rFn;JUTpV|s2tt;=@lQ7_Z+_%8MbBhbsNhIQr__|PsB7ld6rrB zwbX6q^=MC%n0nw=i!*xPkWSY?VkgL)o4wWumeV%oK0!lb$KA@nh%1`1POW zc72Bj4!YrRGvavaWsaYXi4`eP?aw+NbyQnc&3eiSm%{rkC`&fUG?jYn`zFqn_hQoi|QM`y}$>KBre^Klc95;P@>2*Zm2G zYx*wbUo>~WqW3m_#@-^(ZFHXQK6myyZG&ta5Ad&?o?IRJ#M1Ce_&n!k+-r;=?JCCU z=Zxb-pUpm6)Aja>QmXW_&4DxL)=Zt87Q1e*P~crhl}<0clj5iTtSYWmdtTYh|LAJf zv2~@~ybnLe`8U1pJ^E+q_J_;=Gw^+{J9^>}Mth9`yk@LjJ<{hUcy8Uv4>9P+UWFL+ zD?h(~k=6ERcjm7-bMb(;`y2-b>Q0EOT(P-6ZPs+hOzBF~EQ(#YCOpITaar67+ za7jzo9V;#Neo^a}*~XW;=ZEn*9p=LOx1LAbTXkgBW*vUT)2aJQ3wxfVh8I_JtuuYw zG=Edc;;6?P=XXuDST5-@JzBLqZxXlZ)4lHIaZh~9{jaU_SC$q!lNUAj*i#MvwxGb^ z(rG6hE#9trlwG_~}S*VqJEjHI}s;Wz7kk5(J3z+1x&-2}5&GKFG?!(nPCXdzj-9G10 z7G(lX4@xS`f_~Tn9dc45_=8_VktL7RWA%rs^}hA&W&9?fE1lpIyKVU%`RRQ9L~bi@xtaZp>49 z{I}Qot?lVj_S0_|+8NELJ+C;+d&?J>pCK+wD?6@uzUW!(uroS%%e`mw*E#%+J^bMO z(epiJes>n!@6%gZb@s<}@QN6Z8VpPLz2|GJk0>gga(2e==&tA^D$m3}Wj+c2Aid2# zp?uNXWwra0UvBuh#J%SCFXh#BxhH>}IV;1mrSb>^*4xzG-X+zm%DMe#V7e{+O*hSO zUD03REBij}{kl^kKhWOcYya7u@=+6)McIYg8^uesOj(6b`OG^d@ri2&wqNeS* zE;dU&=T%Mg1S_FSUpAE)#LUXRa$REWuQ~6ZL?l@X8!pr0^qi9Ry(GiwW^LZe<+Ii2 zdTo~Fp1oh}<>m{eHQrI}`sbd@mzq7+F1~kn=aQG<+opu%Zn-Y(=lry<{*Uis|EXL5 za7%61y0iP1t=c!uh zoYXZ5Kl0XJkT~nQb@TL~Rl4g03+JR-`aUyl&9-5Z4&};AeY@vb((%e?(JDJ0uW)Qk z`eU?n^M;?-mw)Fwddn(qvqPHI`YF|m`=;$WwBI5<`NV>$E7XEQ{ax=Rvu#^8?a}Ak zk(Z=vOV7+Y0Day2(-zbfQm)>~JkD=glb>K(jRd*Om#Qd1UrNCrNhx?M@%l+QTz)Eeus z2i8Zwujkf1y5(H-gmkNSd)wCML>>?KoSZ6RarMskwB(MiFN?~gUCu0*$z@xjsvRK6 zJYVMb%{%T%_on-P=&$>?|sXRz5RPXXTDh5I9bWg zR>|AquWRYiJ2N`fg=IAhH%Ex)yA-ocxA^euTFm>~0k1#A=Gvh1Ls`YrR|$a@PXm-1TuT(E1YR(MLo(VG1t`vu-G_eL!5414>Gx@^RZ}|*S8lQ_$XK6n_jc0M_gn2`*r)V+ zn@3h}U^^_j%^**ELG;yAX{D)4G`5*uUaT{x--zd#^kku6KJke;C;DE6P5z;2WWXjm z>$!f1x>lLFiqwbK7i>~CU)&ROTe7Zc+fADdH)h)l?7qKyebi>2Z*j&e3fI*406pLO<=z$e=J#S`osC5=bo9UrBvsAPv3k@Yo5w_l@)HG z<}>`?hL?1iOpg5Ya8Y9@#|^Ye5>?(eHO#tP{b1p# z`n69^bDGT-uku(wbFO>0!HUdV>pN}vjJ|x4_A6nk`TTvR#c{?TD>qe|Pkv-x&3neS zVx4`*>orB*rr)i(lit{Do6L0mMv8Z&k9ViX)o-yAUD8~h&nn#!=zF|(J1@(v15KrK zUVLPF-1p(pJmE*;$NNkJv-;9=cD)sEO8*mmb2qd8&P&#h)sE{Gn_5MXn~<8tJ;9BJ=dZ)URt49xPgyJzf7oA;69;d`OH4QooHR!^@wU4QP_ zw(M({lT%E0Pj|Z>n8x>@b8Qgcvd}u`hR74s4$hja>hD}I$!*F^2Ay@4k6L5fe(cEd z=QaDVLfG)kdoSP9MQe9F`T5qeGcUSAvMqj{sncUMt5tkqvFl3j?wuv{dgY{tlVfjH zYldfVpN>7VX0_@^+Y>+JyAmt@+~E%Y@UMSjb#a>KhArRzO}4xtZ5AiFwsdc3GOw(E zX=ZO=#O;$+&MoiD+AiIyD(hj?$tXY9d#d<$ZD>zkZQ#VWT+CC#Z{2H8dL;Jqt>VF# zFPG-GU%%sVBHK?s{$%xw7w`TvEPKK5{2SldYqzG$#l>zd-d598OzKfiHLJ7r<9F0(V`a*+ZtT>xUFO=CU|bZHv2*P|dym&TcjGQ4oj;rR>Ds)7FLf7%ER!hgcvYzKXi}Em zg39!p_vBX>}Z%}*Q2jR>(;+}6YCYW#cB1bme=Q` zf|iyE)N-6($Y8kruw7!Y{mjTd!yO49<@qkgFrL%Y(=XjtzkS=(g++_g9|tYa^4oo5 z!yUjb3|F?U&Up2jTunyZEOaLnOTdcEZtV*Iduu6j-~C{IyZSe!*?tn!o#wBe%HBQ=6DkK?B<-7Q=jf+d%V;Q zO`m+`(1#hpt3Pm6$DFskVJ)n(Zc4!Fg*$n76fDy7KBv32&W*M5z`o2JKi4O7CO>|z zyw%TsX=VQ5H}{2(Y`0TvNiEu3n>^)lwZ52N_R4S1R-Ig{t*M`y7|kr>{VY&N$=q&J ziu+;@Q(apJNhyrR5Agzw*Eru_-S;c6`L}~<`sc2v0n7mkEQhYhuUuBItAD&lIp%lf zAKA7mU)FzOvAw9fG5%fM&VLq(JFCymeY(C^|1bN@^{?w3j%~8`-0yN%K4ACOx}>kC zA4_byt14?;^p)!te8)&Ec*n@`z_?02{;fM0PhfYUoUKpIl^?5ReYCWW2Xl38ymDyK zno@%mt5uit>4(N|eil4a<>G|o%aO0voewJ8ntMZGf?H;I#_i0yWPA0vmG;woUpw!5 zJniA9z<%LTH<9ywdac;o<|8bhH0b7zB$55uDb zc3}pK8R&xG(`VbS3tOE^I#a7@C33yP^NjwJt0&e!Fwdzw@ZR;lcm93YS`S3G?}&Fh z)w_L=prS^DiNT=tLGb|vyi~HZJqCawW_MFpS8I{NTn04x`GYVPv)xqOG z0~?=5UY+-cqQ3VNDs6O|w|^D8e{WCjnXcR&*=>Y->v37`_FK1{kp~Sy-K-z&a=63 z>3DPoEjgi9vFS>uuE~^YRn0)D;uCR23?XwqoX}9MzU4c&TIk%~Q`+@Bvej>0n~Nkv zBwMRj6s#!m==c+^^eyVSUQ#A%sV2&Z zJh;F7t1;wQ>5=-5F=FCu3VD>$Fn;7p#Qyt$M{}~jG z-`XGNO>VyVw5)>FI(l<{PL!0a=d3$9TppYAK2%6uH}T17zb%og6(qaH*;gv;rAEWC zdG9aXS^m)P!-qZh<&@@Z3-4rGaLiJ4&5oBnkBUrt^;))EvRQIZw74KP`P7Q36*j*; zWZE_y6PfvC-R4Qs#;YFRS$9h)+*I!ibLImE##VW0t*Hq=*tsfavTF4kx6~=fWZjH( z-4rryGQZDTn~gHHcXE6tWCSg~y1OrzEyW`~$SatqH||K!37(K=75khw{D?iPFHt47 zCU0|2`O&EExm7{MIy>In?$aocwq5_|uWw4)e+HEaSM|b=*Th?FFq>3+x;I90qEzVZ z0HOZgYGMCWi|PICon?=1NY+U%{FZm-DZn(9#Dd;~#*&~PkJ@wb;Bs|pb*x#>W<#E37aPJRy&K(OkZ94un=H!h( zb0hmBU*G*+Jm+>++Jj%tGqkt%9;vD|&GpW8S-&}OolM!;a|f7ZYIa`DdA*Jzd^Yzs)$_!j*2HU61cXOk%P{C57ykGv~hrh|r( zq8=@Ol7HR4&*04Vtt8_k`+uk1r>8ep^)NEG-ZBLfZ!adV%0 zi=}T_xUN38a>dzrr+$q~DK0BMhrj4kjNO0YtonVy-+G8?4|pVb9c1|^ zXe2rQ_BXq^{Igt;N0N_tAD^Q@<&k8tkEt>S!=SWYxT1ZXMQPtVp6saDwDN-Tc%^Uu z8PqmSFOIf4?HORa_j0k+XN^^hiq_4Vx?{!~$-QxF??Nwq()k#laD8LIR&znB`SH&$u7#oMqbQ z*kc)NRcUm4{p?A?_a(&FiMRVrKWw5dvE%BZ&AL~QtqnffyRh-i8^6`AIkS1(&f6Dr zZ`ihD^Wp8hcXn+1X3foeGd#RVJ>}z5UH#i<3hqXq44faJdhh0Sk=gyBZ_c_-_S(|i zm+MU@lBY!+U9+mwXwlhjxl1&m)NxHEl#&lZISNFB+R&JY`>npz5e52qr zd2{iLOP2grVKaEShwH~%COgH|?|!nLesjIe>b=dX8#~0AivI45&OSCvdrq}Vy8h9P zv)N7yCTFJJ+AMT3!sXVTsQW6j15Tec@iTkvJ2`=8`u)O}v+w!7zUNb0{=L?D*YB{j zsGU+ylWjuy?4PcECT?mj_x7`IS?D>n_qNAtSAVH``ESZigTrl;oq9D5*lcoZqzgaY zk7;`nx9|D(4z<}gZ04L@^lY~4+04yfc`Pnn_BwLHB27NrSnc9YJ7;$0>tBWEwt9vX zPnQ!(l@!!W%QjFEwv*oVSntuZJCDNE%EOf{XRiCsdb&KUl3m4kP1WthM?%^c9o41% z_MZCsOmf}yuHEfVc08$GwNiJ}&WX9be&1I)PEKJJUie4*Lu~Bsug?|D<<%^s&-|UZ z_+^nyeZSC8{|$w6*3EZ2dg`^X^Q0#dQ{|*v9$ft{Ev<0uLg?ip?;-!bd)oLzk1`)_q^(Z%Ox>-UxWRC5ztfq_YhSKk2;_>r zXDGWeGbB~>#OgwgAd!?UjbTOAJ+snhR5_bHzW3*7?e*-KPdX}*PDzJKb2gKsXA0?xp`yv+xj)dX$dD+uetGQ;~im^0~a@b%VkYD9=UGM$vImmv-+)l=4mA2 zFCdx8z#zswa6_-8P)T+64Nv|3^|5}UL%T42jHui}oPZf^8xZ~Iaw}!lRmx7JfuKf^R zs9x|oU2pkTF`LTrO4gd`_h$Wy$mKgV;nB%06Tf+I>mGm48)s1Xy;0D{{B?$28NYOR z)AOp|YU^(lKR9#BxGHvHZhi4IuaoOGcI}C5jrEy6(e|!)yPWdI&TYPny>59O5C~TI zzeZf4=U7t{ zgEpJ%CVl-8%5QPUxaU*nm&Q*&wj|HuT0SAB{LXI8egQeNH#+9=&q6H9JJQ(=vgbBO zw_T38=UDc*Z}xSGBL#PNrsj4%)5^bHb!q33l*`BGom|_w@yW{EU95c{ranBYcJ}5! zwcDo+uFvV(^~m0M!&~b`8#Dhrv{@nHM{S7ltR=VDupK8mnr}7$q zN6sB>-RsuuGAqv*_~6dQ*H=+ESf6!FK1<9`l}?*q6k8&yp?OcBN^qv0S0+8mDDt*B)mxM9Ee@n7R1F?OJ>1 z=|%1{s`vNqDLM1;&2NW7_M*4D)~t7aUDMB1v*h&>FP9p-^@%w#)nWVE{+-TzzpSCLvci)lbd15YWbgC!aY~tEaogZ`a zzxEY-Pfgz0#=X(>&*_SeIcK=5cG@*3%=hSetQ|b}fjD#99hED8G>&YZRm1f@@r}*< zyywPcZ6EGQPuB{|-(jwIcrKr5=98KGJOWlLOb(rDaI-Z!HlB|+HOH)T$Hxm1LZEZ<+Rq(kJho-0pg3uhYBGpOb&Yt3>JxwawgF85j*c$py5Qa__27G80~D_bYxX z(>?wIuZ#I>BJ~bWmWo!t_+tHL)y1zCy?D*1dhPrwk*VUYDtnH(e>kqWW2@?3<3~?- z+}*An{B2UV|B0)Tb92(Jn$LJDdMd_y->r~vzo-kDM(2LkZj;m*PH&S zE~yuGl?chM{61~5Jx{R7>wsewmDe{$W$)gt@vC#o9@}~5Ptz@|xeU-9NY$1T>zpTM zg0-8zxIMVOW1~Zu=j`m7!{Up4mn=QF);fEa<<<{eCD#`F6zMom`c-{g=yF@jS0m4# zt+QurC^EHWH}*UF@cY?6*%R7@msBhjn;I)C8h73OiQ&Ec%SKz*gk>js>^6>6f zKJ~+LmIsg7_^P{xZ@qZ2%sA%D5BFk~NvI`?SB`uziOZJN1{G#`i=H?{~502PrST;jsF{FRFn9x@>~8e__tPn zBmdq13@h#{FRzc7e}nx_|1^ov#{%>(j-`u}R^$GGt_A}*wZJYa_;Y@b@ zErU9^tLxtXb^9r6{p-lT19ng9jqJbXTK{L5aqU0DmW00npXWF8zq<5W;P3rky`QYg zzjpp@s6Fv7rT*2e^8XBmYyUH39Q?KLxjmcw)urDQfA9Y){iNjmYvJFFpU-^gdKUdr z)#y&H@DZ^JbHM)-@kLVJG}ZW=_3P_ey-}Pa#(M{P5~n&J+ckkcvd+I|rGGl~ zr;n`SR|PhSyx zYQIrE_mghbM*)}3L0z?dDZie6VrBV0vkr6=!6aGN!Y6iqM|b=s;E&YrJ=a?+mu-w! z+Fi;q)9vM&D`|_@Pui4odh7K?Ue+sACThCgRQee|)h~C+o%aTbf1=h$T>ZBoagSZr z$q%uGJN8f6_Q&Ku!^3;;AMBKfK)0)E&QI5>!;{x6(>Z?YPH>!}y21X*;Y#}(?r!|e zyjMI||Lt#izxK0A*3a)>l#TwJ=YK6N$uSPmydH=Oo->>ccd&<=K+;JVsv zf94C^>YG;Q(7sD`o%ST0z z{`&t6HymxFL5n)hb=|ZsYOb@`we+>U(#Pm2QOO(I`If$# zew53=YWk{p5sd>N;ef@=pakI5Ll-IbCAH9WIBkykE~~#w^;Vo-mH+YP$6vF43F__5 zxv_lH{Y;gmvn0Y@tChW~ru%EIG@mQ<9Cl(!>eU#({|wWUC*Mx`6Ms6)`j2#Y^X9x$ zHcNRnT*p}S2w&0{*}rwivOO+&o)emSa$K1mI4Nk- ztEB#bD+VTWxNj|6S#{5DZhv05-rb$5++}YMT6z^vnHAU*l)LScXXqSDjpdqFu4425 zCYOl6KfTs&&5HWdE~UTTz4-n+aAAGy@!yJ<->=D^+`g{;*RdD+_oqkIMh4bTYk$3a z@%^`Ut$)kq-!$KkxUbG1%m20aLi~QcEx$G``p=-tf4$s&|E*f?U+?U1vhUlt&rfcb z{A=k2`|DyezoxqWi;`dWuD^cEFY&K;>NoM%CfCfdEwR65?pXI{*X38Iy8mpkkG(7R zpCR*${_7q88B*ka9s6lf^`d@FUen*7rOU5Q75|x0w|kfEzsoP;uNVG1Vf(fBQ^D5@ z{~03gvi|<`+JCi{{-+Cn%S)>NF2AsU-Q&L#s$WY#dH8zazm2=NzdyY;KP)8v=|Zp{ z{lR`z_Oo}**h%N3fgYA*(=Cxsm5n3 zA(bWFD`(C;`@+S=VtVv1YmujV85cr=Zo@RaRD8YY{lU3>mw2qTUi$d0-`Fiz!nrZl z`D2>%v{NdsQzgHh_1$x%O~&iOl9`hxrKCO)3R8>ItJ7WcMtj2U)~Y*s(Q~AxsB$%I~D~^4eCYbekRhDu1g&9*ajr6W$ zc7JgfSa<(P+T`~gf2X@loG7`{*4j0^xp>#hK*PvVV#e&s-y6;Q>=NO(}+4rn$mS53-24ByMF|9Xt z7&?bUsp~CCsmR(bJ1zc*k^0?@OMObecv{UmVzGm7MMw7~B@350N1c~5y^XoVS4Uo& z^xZr8>-V3M&!^kI5PrHY^YrD4%$|4F)AsOPJ@%BVqN42Je+DU?o=1C*U_H1U?*3+bR({6jdiEV9skLkE3;s4ovn;ip@>}H3${MDx>}{D^5-We- z^#7{)qs4L&^QQT)fYhsu9_u#Ul*tUnX|V|?s58B``?qY-amNF`M2j9fAy}+-xg2*Gn|=W-*n69 zw7g^c&bWhxZaHPp;+Z-r6Pqbmmm}=EV!!fOz3#sgZK7-cZu!%8`OEe%b#gDJRji-8 zKIWMC%$>8XPuEMuzXGpRE$mBqpK=#`eC4k7pyMk8Hr-R@MOuuIKku{Nv2UwS6gjV1 zZ_|GA+jjW{McABz5UYN4vP@>hrJHvBuC+6DC1-VXf_4$koQ2jMmMzyk_I78~{4GfBbGd6gvIo3dk@ZQZ5cM{Zxfe%^F?`oi5x#@Q7e7p9o4JAG`?t4S)_%R_z(D=I|p zSa+-U=*Npbm)8dKMb#~?=kn^idei-i@8#pWW`FxpHdW${2EsXD7C*H0GX>oE$vz6c z&3fj4h6!QO_mX$cU8btjv}tq3dVG%^l;&} zbHN?zi53sPNltN_Y-09#wa$FkE47cy+n(S0eE(5x|BsqD7f0KGCri&(Rqbz^bZz70 zvftJx_UC5qm-}J9(L3#^rg6l|2~V$kPmQ-T{;=F}!WYj8PffO|9Gxn8)L6-;Ek zuiVXc-$c=Sy@{HOq?b zJ&9jTwRl@3eJ!O@!Y|zED7snLKA}B2%7c3yL+_kzPv=c~$k%F-N$Q3vyLc`;{_xzE33q1SUh!7bBh%%k{+4x}$_6aGy^1>vAD>FO zl5;Y3C9l+lW!G}|+=_8r$*mM6dNZ(8S;@%Wg82@^vH84}`{xuzPoJl1Q*yFkPO$WG z3)k(XdOHpkX=!fzr1#lFnm@*N_KowWlFvsiaG4&j%8@y#!_#TP;P3-{_Lc1|mWWGxmh)csPiFlJo3r)N zACFm1-aEOh^W(mSWv})e{Shzebh=`S3DU89`(Ufw`8MtOB(Hw!PF*988eGoS-NX*} z>9c9^6?@CBo||#>!p*O-MsGA#N=?3X-qp!kPE1)NEC&@DI&gI_|R9ttJ#XBjzo_2fYmmg|H)1Q7lGQ0KL z()~rf{E({(&)?K}u}Vk1+`YyB-pUU*e!Eo6{lng1CK)Bf=nyg2ncs_x-$wr!d}L;jqsNqo5IPSWxTPo6L^Ffe~!$;bH1l7WGt z=5oE`>HiFy?SD>cwF|$s-EO*m;vCy!=cVeut6sf-@S5;%sWo!nzFhuRc=|uXOoMvX zEveJ&n)!?NHa+UO@z(VD$EQ8#3g=f?X3Uu>H+R){`L1={otr*h->n`!HT;pOCGTHV z-a{g8L964py?AsZJnWuX#}qf$3yd2UpV_LoJfe2l{lstAPM_J+vg7hui^@Ykn?# zyi{FbBm)#YYPLI9vddAJeRqOvoX}$Dj>|dX&eJeD3^^`leIVa7HdCi`>@_J-| z!26@e)%0pA*fP%x%XC}c`H`1fzoO#Z?6->TZ+>^pvq+hl=D%yhFPU?njjx>DwrkF| zYqJ$Xo@*@gEWV^Yq2n*lm5bjR=KNFHwLAYuK|yez%%aEVRVo`dWLfy1(yiY6eQw$H zdq3;Wo!E9>`kR}(b8qh5>bc(2{uXVzV=mHar~FkwW!r0$kI@sBdD?{vh+m&=y*i=D z{7C+x#qBm_w5I#Vt9dzwoid(S3!f zi(kIDd%mnJFkNrcX0fXhCU>R@PcsTv-JZJdb)b~l@2e*hcpJCe7u{KVjE_GrL+8_1 z>)G?8&6@r**qy4BJh`V`Yv+X>YL~Y*Wz|aP?a$Q@ecda)Mrp^dS6PYS8|9pCcz=#m zyBkvGa7}+|OzUHP(H#$`y#J^7<84v##5gr6pN)Suy?mO_b34Cq_XNo`+^@pQJTzVj}EhvA_qE`v|k}HiZrR77jb=&o%O)qUeTicdal>K;R zb`np|Vwtsjs-(FKy$W-V78Nd3TWl&76zji&=>ZLzR9H9s)UK!x?)ew}cOs8=^zY1{ zyvtr@|7Un2@I78+TE)trQ4(q3qHym-{kM!irT%S6o(UTD>%aA@_4aCerF!n)iYa@% zq>^uRUDE{LlO`e@?Xz3TzqOLjf5oeXu}9A9E$))-@@ppc*zFmFj?V=v_#-n1TCg!r ztP{ASdduckXP(O4?)2ES!?jCS9!iQyU%V%FzLr!fzq{x1v$uk-rCwk7L_l9@QT{3=Rzi8@zhGY9p9CkPtc08>v_J8MC_cmJl>+Sr1CL7fr)~?LI zv)CDaMVggXHz&3$(&8uX-y!z&AFr|gAFhno??_H-FA4W;hzDp7$y8so?Z!Q?+bU)F$QMiuHRlaaO_=N2hzm z4^~|A&`;Si@yVWyGW_dgia3i%-vuU2Y)jzCx$eH8y+R^vQRU&1Nns3f^X!wQ<+WtJjRxecy}S zIq$K1ed*ySj@hQmKiPG>aL)LvVpV@x&;R9q-Lx<7<&Nw=^ZUm3;^gpR!^hD(v!1;3 zTkGHTcjw6+Ki7GmS)vjC?B>R-BeGMrZ;j^_|1jg9R9>_L1H;c{|C*=&XGp96tjblh zersN>Uff}e>fU)K{~6wSuioFhM*W*)Ew-*hwF`p3!Wu2(*XR>u^DyXD5PPnr{Z!@Ot5Q!(q^UtPYh*|=k-)}v$7ZP!Gr zb!z#RJl$}+J5@;Nd&x)BJ3Cm^Hhon8n7{2{N>)t$!SaH~clJ(=+Y@T}_u7psMi%kz zGjyiLy*rwpVmf{Q#FzwuyZ;$(`CJo;_pfZySoU-G)uW7ZmB|OoZES7d+M8^V{g-g; z=&iD<*@H^rph{-n(0oU*){#$6Yg2?3g6%D;2yp{D65>>T`udsd)?eG&2?K zR_7)DnE$kf^YD-2m3wr)?Y`4}b56#=FaMNJ?6BsJi2g8dz1Z2WN8R-0OI9b@PCalg zQ8Z08X}4Z)Z2FU)V7qB>n>e8%^ei7?jqx)wVv7fN0eJ;-V;8N zr%VyuZXjuB}#PPKoi;w0MS*#6+x^vw+&}PlO(CqgzmE0joB{y?= zHyapie4klTcXxL1iF5v+l$F+=H?}R=lYUs-^;v+=yk7187_m)zj>v6ZJ+HZNcSdAe zq0aA{OEz2P2Mb+s*>`Cg>xnD-gl9}w{+arC-*KagYmcqYPoMd2)`Cm3tDdpS@ACFh z+8w&->NeMvo5Y^pxRt#5!`n{x*raxmGbKCibn?CmNju&0kYQafZxgUn{bA&}`&_Xf zolDLuZ|xH~xBl^W!!pmst z?^SZ#Y3adaRkU0GqvX@$t#zk9{JapyeOte$Y+^|$oBD>i=RTeF?9MuM>JI0fyqrJo zJy$B)oD(y*7`<6`sA1*`P2R8rN#?mPg<^_#IUmT?J|+>qbx!u&_@%5CV=WI^1Dc5){r5ueupDkazdc#)rMLpc0W)ie)q8f#5 zhC%h+qQ8E|Kl8tNNV zpOb&suM&$svT)fu*KF}K^JDfoESUFM7u>l{Ua{V$o&DAw210&NjptuKJ$&omHRb_V za+>-MN$=fS=9%yM?zyX@RL8|z(LFQ8Pp!C;;<8?IX?U*GbGCT)lttH%D)+5OT+&4-wP}sf%Il1#OQ7Pknnf|4!52 zTie24Z@d2|Gs*YR@1XlT-P%99zvN55{PfMVl29>WDs@)nw1NVrYPxxPd0bg4KT}ib zRa!kqM?3F|<(fHN?raW)Hu@G9qPW~b`ySp5UH9?+)aT&=U)Ai>)72whv#M@?;xp-q z$9lD0zdmjkcqNpj&iOpzbCq^Wb^qx-YtPoozIgMq-Sx7mOy8Q~)_JeatVzDRTUXyZ z(<^v#OVsv>wWcbwA3dJC_TKkdk2i0oxjs!UG0UFoD!6U^wUy?AwKDhDC+g1BxKlAHmVbS`#;;f5TE*t?4xf-J zovR;EIU~GiY2BscyWS-q)M-CjC1Vrz*mqs9XK=3T>*DXdGd}EEbv^Cv?=y9iI$gXx zCrw(Us_JQEvZ80##EH`Kk#9wBs2J41(@+ z_Jk~b&M&lmy>OAuzWa=e?^Oiv2%VSc9Lsj=UYqeX({EZc*|wcLw$Que+@9GcJGM+u zO-o5habwtLwVJtqwQ1e4VOh7KVtamO>MVk;N$wMHzr%I z`c#^ce?!&F$bUiyV_bvcbn_pN_f^`iUS)c&?U~)dhpF8&wN#!<{@Zw9!<*~+r#6JY z%2}PeVOQtQ;$Au4MBSHbJ(J!s&Uuk{T<=hbmjUaUN9!d^{dsqMUA5!%lC0D#*|yF- z#=XgBZ+d?_^y=i*lan(mC#8hBTxLz3oLb4-vfEmd<#uLO$@Z`*ifSGnLM&7(EYJ+X zI+lFb6*87Q$zW~%+vQKpW-r}8LGj%_O`VVa*3LXalaNTt_@t4YxCps4)@mTOK>n z=DW0vzv}j*v}LcW?84et#2h%)?PpY@|77uv%Xha1?PJ<5`*Z=^nIH!9CD0YTGILW6 z)9kK2n)NZ&D1S=zv-&6CTd2}AB|phu^!m^6IDVEwjN*Rxc^UKd_)w!asE$?1@==^=vPFg;rUwGfa3Ik>(25$Wm_wR5Q z{byjCIsG52iR<0Gzx~Db&vfn|d?zazpY4CgKH=I?d+>?)HYU+MA)xc{*}Eo9UFs1j zG-1oEkjbAlwe-4_f4I5XrHX3oI2ooJ*!0Wv$1@YFUxAvs^-Z|+lCfIYoc6;~X z+xrul0$NO3%x-z|Zkw`@!+)ZN6UU?}OZK`f*(Gp=!^!NyLY65?le`a1dlKz$_@#(J z-sJv;qW=tU&i_kU=s)$_+Ve|K-$>^#+9xXQG57bci?RI>%a3wj~HpsF*@YAbjdBpG#|5jQBtMH?nVhL(BfVHi2u-8^&MNU z%bDpbf9>`Aee35k+w~uO4P)!B`EJfF>REPh)+vw48{VXveUG@qS#)3N%WJoVj*f!g z=IJ$XPGIPl>3O9^{cjjck9N>dpi4qPfZA~be%r^ zq}VQ#m0JI%vN5dx>UaBqdD_Z@=6T!qxjx%;vm#gR;9;Zmhv%22-R3*-T5{*oJu%uV zu6_%>_&n6Z`AMhGrq>zao4tH`I(SMS?K7*BzB%vQ;%i%~FWn5-bhcryZ_B%ES<9=bg)s-5=t6 zs=YNYOMW|i?o)5?x>c|9w>@Q>dU5yin#EoLcU-i&PS+mIUQu9kY;o*nqb+|mc_Q{* zx$pAgKSSZ2mD&M9bAIL@OL`ps>#^MX>#axsGZeIL+PC-k1Xms5JBdp=*4l11e*4Sw zv{75YZn=47>Mv@KFRgQ8=&MQm81^t-&n($0KlJ&`t$G)>A5(oE5hl9RWZ#1e*Q=eG z8*hf3xgK)-j)kAZ+-^s~*2(Wztqh9qm-zjBR_^*VhMjxne&l!<&pUtl+9&f>Oxre` z{A9Ux>yB&esWo@kojmdEj#od|+FiEA{flm!ZA^;UnX7zBkmb?VdqQ&xIbJPda?g1? zf4#}JJ!i_!-8_?F`fl6x8-M#kXFZ!4+AV4#ahEliYogV$Qzfmcyk?h#FKX&t?%T0s zTg%EUFCPxcNy;*Z=a?| zm$0KW?&R8bMpxC0HI*-21%Cf~zEe4panb_@#>DlO#!El$b4!>Lu;+Am?B24xZKunw zady~y*1q_uC)jlBdhpZD3sO4P#uP(s{$v9Kn+Ui!+ZSL8#IZRDO4N`C3 zx^?q(5^HX14x7wWt3!uMmP>VW@o22oJRV!2@Nde*ij?&jmRBdr?@H6nSp$xbxcwfarG z)}i8@+9Spj5`O;UogR8I&B83vASkWTAY8n|E3Cb;@aH1UcvcpO;F^cd1Y;qGk4CmJYBsg z{uXE^u%}qPTJ5gzWAE+Z&-@?!TM?Nr)HZvk=$d%&t-ejwXSNe{UX2a=!H;1Kpj#_h z;rAX!7JXfJ{d8&WqUDb#op|i|V&>N(?>()CY%Wu^H`IF0RB6&X-Qwo5WXYAOlRR3( z^kUz5evGP?`+DOv+uqZ&@(q)}TVAYvQoH8bN%tS&trgmCtAkZ$ou9meuV%;A$@9Vt zC!7e*S;RW&^u^Q@k&}dG6bJv5TzOS+#e{uxr+j&}*jUm`Z)NX9kJD#%{FXnG!oU8@ ze}<@wA)9p8*CZTU-(`1G|FL`Bd9HiEIpx{~UJ1PR_U_%$vUa<`qA0P}iJII_m(r#x zdfF{XePSeT+jHR7l<1ySOSUgQgYX^Pn-Pp*RWYFDbT3}7)|_oW!R}Gey&T9?g8JW% z)!UM9_E$=7(!c2S_12$@Ob>aFDNeax`Juwsp_2LcmN)CK=kEV;>BvIJB!bYh`b#|L zms5KZ0i2d7>mp<}y%$MU`pbgg%`zi)yW(d%2?J)tp_&tTXds zzcF*WJ&HV9XPx#r#qadm)u$7-CdzK-^+@y7%uq62bB5dXQTMSpn-@9f57+Bnt_qLJ zt~s1var`G*nD`xYSR|N~PiW@5(mE#^2XDZuS|P{*V+^Z_bGfE%VV$)e<>g zvF%FA+0;vRANh}|pFOgsXUbIZjGmrd4R(6P**sjc@10Eh;TmV?&n z&9ZHpn{?D;AN=eypKw5J!hH8V>rbtg+UT=o{t}r>=Dlf;dfnJcOjzG83tVKf%(qhW z6gCD*=h7mf>D`EuH{AElE9O&bq|1-(&A7qdVtLZBmV8RY_Uf^&(gIu-YQ` z39lFq?z{7-uJ&j>*Lt3HX8BfGQK#=YR~-8|-R!#z+)-Y!zTXQvbX1@4cI@8yrZ03xU+PiCJ=3H~j@ORL8-d`?h@v-4U_N17m!(Wzd zUUctvc~!yLw-a|fdiZ5s&YcY_e@ZSX+_htyu`Z&`0cXV~)4l(!e?hoHX@4tS&X2FANzopn5jm{-oHhnz#ZR-_xA>HcDojfz7 zeHj>d6e~8mO^$XW#NN@*E~z(#eSyh z8K*sphz;p;cz<(SzfJ74po*K5dQP%!VpfXDDYw2Yr(9=xXWumegWr43dp@eFZvrd|8dxBBv}m(f!5RgV<>3VWDroTryNJz6jLEh>T_>9Z@aDc&@@cwamx2^Q#LG0 z30}QBG+jzcHT>G)=)R*2Nw)HK| zo2q8f8#k_TPJVe;b#79GRq>vv-w~m)o@Kk0x1L&ZZbQ*ZcRQt+2`f%AXYH9B^j3SG zSGMixz!N#!rLRP!?LNCvW_NGE$DF)4*-fA3OiQilsZNYZ-8A{#tVNf3leBvc#Vn=$ zC%2?6JH5~QL1ocx(HoZcMR#g9zl!>?ZMV0{h3b02>!O!)7F~9pJvU|7e+Gr6N0m=+ zo3rg<*s-E-#Vp(B21V!EhAdW6HezOqIzD-x$~@s3|3^%g?T7bS9en7w_;AJ7P zWUPHVEq(Xr{tq(EU9Ym%?%k-lwxFxFaQfrCXZ*)v_xMyDyq|s9dUcSzb8QIA*N^W{ z<$knh=NItaQH?#yi)Gnp4y$Wr=)C*o?E_lkNJ}1;r-Xi-Gxcs+^;6fJY3VnV6fE_`cfZR&&U`GIb8(Vwoc@GY_xWqrW(TvjB|A;r zkXO3x=&`&{r3R-2nzrp*v9N7s-=95Gk2+n;iVu~Lgfwx%6(h+6*5y}wuf#)_UkO5% zUzP0sQYZIfTEuq9*znm;&t6Rb)PCmry7?0f_e{#U7{B$aonqOmKSzJq2Bz-uvdViB zHnHa9*V9k9INuxXN&68#;mPs9_^?Vo{w;SIWUe-d5_hF^$+qv%A znY1;b^=_?;_bl5{di;{F$_>>;GFQtdDQWr~^Y%1;FL1lolS6N>Yn|EB$-NuxwCmVE z$|tsG@9AIoy7SJ|ea{Ww?PQsDci%1RdDm{=xqa{C-?f&8>o@;ra9clB|IRgs?K-b8^Cd_w3C~@fkPk_f2^9o8zt2$t`8mZ%T@R8)GLw#C)*tI1wHHgg4*y(cV0} zABRF~3T|Dx_2W*(_)aZ2A}X4`s8OikQD=+>Bp$yZ_^Mmbsu7Si#9~yzNPo;@|WMHzb&L5r-z?CnqQDRb-A(jE|0Kq zi9NjS?eP|8sxv+=(c*dWMl<)jS78Rz(U<4!ii;=T`FL68?z49*uG~pITrqLA**f9l zb#~c*ckTT-{q9@dmu%hgH)ogI-k9*=jlB5kMeVJZL}O)LEGDUbTe4Tl`Ro_(JjU8T z9zXVO|7mR9Fv(O!CaJil`lImU{VHXJPj*UeeCl%5>YiH8xo~rNm1~i|D<^K+edAwm zTwPxf$JL)k)^-7^;fm`kZC#~ zY1x(cpKtlS(_awo@ow=xzcp7Dn+W;vxlftF=e_mE$rF2{+e{h%98P&LcXz|1oT#%_ zr?zj3IUfJS;;nF4n5$gm^#ethjxMVFlzBOGvuT;=<%Nyn6+aJWv-}9j%47b-IqS!? zbsyR#AHBQlW^B9q)(_()FK_(1tye9-xymFg=XTTY^_GHPO>V`QD7sD3S6~+Xe#iY| z&0QITV|!G-{S!Xc#jAGCu7c5VOI=f*1ZSoU7!IcA5e zpauOh4aqa^zYab8{X953hduh$<%e6tX6{;eXG3p6Mm<$7X+=7uhcr8E&Mh|ZsmIw zE0%dr^kpmeKGHrc(=2qi>a_2N{Zn_%o?x}T_G40<<&HN$CD(adU)*l-uDB&Whr2@R zb4=8MB`0-Ho$EU#5>XW2&-mWUXiakQC)9MTI&EgtPWhbZZWPJ1`d)0O8(+uY~xq9sIEnT_cm*JZt&kcPK zV_zScQfVFQ!L|Cx>$aQi=0EoAO-_^;uy zzjA4~R+(C0Ly=nDe})^;>ytBXUK6{)C%bx8V)hy#wXFxh&sfx3#A?{++!#CndVd zk?bea!s})uz7vyV4uXuZJcVfg*lkQX7KW1zd)a9I%c|GF&EvGZT zwG9;aeSM_fvt8A~dX+`gbI~1&$77uPZib7$eG_xYeCgdkvcImi+_Xr29Z)J}8Nd4D zuU_`vRc1zEm&8lnPWj%uso}Jy`kYz?|8=2Rey%Uq3!dUjFDPQ`ytlhLwXlzG#*=pL zvc8w=FWtOWys99!S?_z?`@4}-ZT+XO+E>OuF{S9G&Z*MCtEaj(w_SSZ^=1}BqfE>- znT}`v#(QeEzI$@Y^x2Br){{GJ;vVn)=X&f-yTX}*bGA=smMy+3kX3rhjCYGn*>S6z zyY9_bNlJ4!vxz#Q6>{#Gwcv!d29b9!AC~t#d-LJy>JL{}KKjnQWt#l*kG`5cC(mz; zeO;Z%Jh9MZT5;38tB0>unD|D9UF)vbE9cjauJ*nera!suNm;VgQ{8V2HAAJl-QRXy*WGmc=hbD|Irpv|`8Fkdp_pXPqlh(CmzV56wkERhzW0pRE24b0 z0?TV#lpI9GmA}qQ-&taJi-7bSo6Kf}CR?m`dTHB9*>OwKLYanf{+UDT)l4Ds)) zYdfvWvwKsgChu(B!z;Z=^|epl@3@rPMYqNJ?kqRr4p+YHGErgnm)7!>1dla~zFj`b zt@yF_!;=%MKHiekeQl#2`Ek17-5EX7Gb`nxGDC-pjLhTA!XHUUv6+cue1w zDs%Otn>wagZFf0&{ffBMeS_O&7BNCQCYI-Xk=hnJ`NiA)SGP3?-JdLz{dC`q=RWcR z_xQ8_rtVw*hL_RvSe@s`$;mm(OXjSZE_?7Zx6bMCT-)f)Q+v`xL~oX9ef`>+^6Pbk zXQ$T2C$qiPPfWQX`n@rcb%_o%Xe%zy`fif*_JwG zZJsN$cInk-2lo4%`xLhLmjC9xoAdU`n7>)J((v}X*;TM6t?5v^-INH z70y3hze%wryFS-mam!u3zw+(7RNM6bp1*G2m&|+Sty%GOJCpq-@t5kO|8^WV;@s}? zyEKP$O)-#%Hq>|NZ#D7Vk**MBOtRo|SJoEPUjiKvUM5l)6R29K1j39om%HSu0> z%&x~T)jEBIF7jpVJ}D@&ELnBQ%y>DIpZ9e(|yKwxw~-El4)Nm8=m#9^m^)a<5 zeoFPT(2pIVw+-K%J=1fO|Ha~;%b=^%QvVA6kzKZ6PQ%|@X5p{5J^qt>P(2Z}Kutg4 z!oQ5a3TJ-R?45L;YtfEr1pDpp%PMoThe^znGXv)3T*{&i_ZhznNO=WWK z524U>xA0q^zw_TP-RZ;acdGuI?)Mq{|F9Oms=v|R?#&;t{MOF-SKrqg9G#Ntd;VJb z`vp<;3-kATtg}DSZuj|$-P1Gw)>r>*w=w!)AKCA)Jm$aQP>W?`@sm z#hicjeg6!L)CYB&iquP@~v5K{j-dALwD@3W#QhjBk+659@aJM+rUMg6~VEl&2?PRgAE{m;Ru|F8HELJ%hvMjdz#UI;?I)?S{>URDqmdH!b+&pLJ z$ECj){hR#j{u?*T3BJ43k4?{7|I{NgKVMbWyyz>}CG(kf`%j7(-#fEey66+=kd5<- z2fuA+0IhAu;!@@#MWy&Vy{Uh${pq<;_2x#$%;OU-nq(@k*jAbyB;}vJO6k1GuFq^s zwzZT-#lE}cpSa0=PK;rxPEVHB$LJ6C4}aLWNvs%N1y1#J7KRfR2$7MU;=cj7j zO%1Alo#*-bZR3j%F|*&*{k(U$?&_^s5%2HWow9PN-nK37y_k2TDtF|zOVxf-KEHUn z#eWn?*k5hy8Sq9!5yRmO%1%_RzKmQ z9f1N3ODxb>2pz1^!1-#UGifhC+b+9)Z9Y*@ zruG7DnhJR$2VDw__ZbzwPFFF0Tk*N@iBGZGqMxBxZ|{4(&cr*&I`7!bTVI+>br*E9 zJeoFTlF*cI!B_eocdvYsY-N5t|G?5!S0<;t z@o#(HIAPlE6ThCd-FcqB??}8n%dM};duwcGN7lAhwlgp=z5-pJ2Okpo)&(09vG17} z$}E5St+JW(FO7eh>(5GybL4&HvDd%7IUvuW?ya=;*V~2v%pO1%(}-`#m~T=yA+6Rf z?qqsvVB(-X|3UQ`bakdP=e2(~>zp&IVt+Epy_ouA`^^3EPkPLax7=J8{o~T#iqW4rmJUxQ>*97Q0k>x(Dg)J95_jLdP*z zL<-u5oPu)A_C~yq=TEy9pBR^Td?jd^zg^#^nVWvHe{8>{xn3+>^{4y3lz&F9Aw2c^ zTi^|;oEXL3R}B8_&pYe5lkxYKob}gpkN>#c1YQ!lBktg$nv1_2!K<<6VPB1f7JPKz zVx2Uf1)VhKQIGze`jdC*%kn22)r-O(-AnshKgr+vS%bd?=3ToFL;Bz3!SIj55qqDtxS9(N=OkJ1} zxHKcmXUbGnPa~6+YOx-tPr0%4YHHc8oTMFHl%1Gh*UuKSzvPQW5!P1hcOyrtLq zk}p5*@4kalQ#_50g3jNRcPu)3)u`~#wI6SP$RA1LU0=l3xwbHFyL|OC`@Xg#_dhP) z`LkN=QO)an∾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 U@6QtpyK4{F6|gsY>;K;b0LiN5N&o-= diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-timetable-dialog.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/3-timetable-dialog.jpg index 968fccdbe91439e686a63b41c266e0529a2be928..0e62c2e1016528730514872980658fafad828750 100644 GIT binary patch literal 232165 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%0At}F?i7j)T) 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 diff --git a/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-exams.jpg b/app/src/main/play/listings/pl-PL/graphics/phone-screenshots/4-exams.jpg index 3f49e774a214400ea0aa9d305963793d2746f7db..980993af1c49742d6b2ebbbacb2180dae61a05d9 100644 GIT binary patch literal 175412 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

;SD#A^UQ+S( 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

t-PPf$_ke8*9sdg)ZI| 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

65fQ&+x^KX?9{-_>1PmwtJ#^f{}zX5Q7RzgEgg z>wcBjZd~%~j;`3XRdZFR{mx#0EMrqly4I3h(Rb%VzIJLQ=bJ0f)DDc5zJ2cRRKc20 z>uxU&UG?HtnWgi|SH&l`7Ija~`nvMgq8;~_oSXGpMor!0TUoa7i_dxr8{=>Mdbuph zcxk|cZ8dEf%l7)bUUT(pXs6VoO(v6kr%qh7Ji0t2_U_Fy?=rg7W3#5upZ+OY^jGbk z(-uc-_MR>@bjdkta(TVvC5!X(clS+PKI7!q_Oo(biMwo{mGAv?GAt~#!hPS*->;WX z^R%qeb9wUgrP=+tx8FXwvn6|O3V z1EsF`Ypq(PSUJ~xllOFu%`SI#S_KQPIiKP#cB`bD!`I@nPdQg;*6Hkp$y%a&`{v%> z7wVsxQhep*nb%hP0?rrf&;6`@_xzN)UJYvW#N%}mT*x;%XQ)IWV8cXuxjdV5Rgc?$yw z@N=)0Qd~OgZd&T(;6g6zBLVNWe72rt)s-4|wJS}_cJ`aui}X%!%Ik7DwurfXXY*l} z(7!)^6s=r%zwS%zT_OGAvSa=K8IHJ};Z|I}E4%Tfn@`}BpeOI|Ye-Swo`=stf z*H5`s>n6VH=vY^^dV0!h-DOREQCoiVT$OvNwmz})oxN|x=IhD6o-3ETzV*=Evp#!b z;F&7@D9cq{^ISELx$Mt)m+@hD*1qFalg`)ga24rP&iy()v{XOnUB`-RTi@kxSs(jn z_vFn?7cT?^M!m>CgLat#-E1vXz%tFLSwF5?)qXDZCPV|_xsp(;H_@zaQ-gT_a zj(Wa)%X2OLq<5`l30_szhutQ>Oj~r{)Si7yXms9Bo}#1CBBg%sT~j8Rxw<(%m*{w= zu6JU#+KahfOJ3NfEql35bM+3}^M0SRJhO7Pmvm|+b)~y}eepD9O-OOo<@94$|G0dV zkIG*jv21c;*jc+n8e)B?Gm>xm3eQ_#-uK=1UGem_=fd}%TD31!hJD&Oi-JqNE?0TV zBaBw7e6`fmnV`Ba?aJPX+ZWEf?0R0RV%^h;?~3x=n*3Fqc0E4C)?Ub5(Di%H}7DrgPJ~<}{Y6Q(y4Sc{a=0apCn_QYQ_)OLawM z1l+@(c})yj93&Dcd@OdS^sb*}Ql%W#-?r=s{l|OX{&8?^avJ;6%YCdC)tA=Xy1qi9 zbkbY@%FQQ!W~R(~sk0_~&T+%?>|N83R!_)ezJK?NxZnD!)9Ke|xAG=$dMzq-;iBKI z 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 D6eXA& literal 276565 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 @RN~ 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