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 01/25] 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 02/25] 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 03/25] 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 04/25] 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 05/25] 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 06/25] 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 07/25] 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 08/25] 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 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 A&j0`b 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/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7835db902..3773093b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ - + + @@ -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 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index dd67b87716351217f708ad5a0ed185d8bd6321e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5740 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 JPDHLkV1lpA?d$*m diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 71d1767ed..874b121f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,6 +74,11 @@ Recover 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 09/25] 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 10/25] 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 11/25] 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 12/25] 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 13/25] 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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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d7e66b5c6..f42e62f37 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.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a735c378f1359919d1d46c28317317b574da2d2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 21:42:21 +0000 Subject: [PATCH 14/25] 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 15/25] 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 16/25] 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 17/25] 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 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 18/25] 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 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 19/25] 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 20/25] 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 21/25] 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 22/25] 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 23/25] 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 24/25] 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 25/25] 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