diff --git a/.travis.yml b/.travis.yml index c9f9bbe16..e8366be2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.20.0 + - 0.20.1 android: licenses: diff --git a/README.en.md b/README.en.md index 7444cccae..28cce1c34 100644 --- a/README.en.md +++ b/README.en.md @@ -47,7 +47,6 @@ You can also download a [development version](https://wulkanowy.github.io/#downl * [Wulkanowy SDK](https://github.com/wulkanowy/sdk) -* [RxJava 2](https://github.com/ReactiveX/RxJava) * [Dagger 2](https://github.com/google/dagger) * [Room](https://developer.android.com/topic/libraries/architecture/room) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) diff --git a/README.md b/README.md index 61b13444a..02e1900c8 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa ## Zbudowana za pomocą * [Wulkanowy SDK](https://github.com/wulkanowy/SDK) -* [RxJava 2](https://github.com/ReactiveX/RxJava) * [Dagger 2](https://github.com/google/dagger) * [Room](https://developer.android.com/topic/libraries/architecture/room) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) diff --git a/app/build.gradle b/app/build.gradle index baa9149b1..eb9329787 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,7 +126,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.20.0" + implementation "io.github.wulkanowy:sdk:0.20.1" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' @@ -145,9 +145,9 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - implementation "androidx.constraintlayout:constraintlayout:1.1.3" + implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" - implementation "com.google.android.material:material:1.1.0" + implementation "com.google.android.material:material:1.2.0" implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" @@ -181,10 +181,10 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.1' playImplementation 'com.google.firebase:firebase-analytics:17.5.0' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.0' + playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1" playImplementation 'com.google.firebase:firebase-messaging:20.2.4' - playImplementation 'com.google.firebase:firebase-crashlytics:17.1.1' + playImplementation 'com.google.firebase:firebase-crashlytics:17.2.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" @@ -196,8 +196,8 @@ dependencies { testImplementation "io.mockk:mockk:$mockk" testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9' - androidTestImplementation "androidx.test:core:1.2.0" - androidTestImplementation "androidx.test:runner:1.2.0" + androidTestImplementation "androidx.test:core:1.3.0" + androidTestImplementation "androidx.test:runner:1.3.0" androidTestImplementation "androidx.test.ext:junit:1.1.2" androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "androidx.room:room-testing:$room" diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt index e3fdf01be..57f3005ad 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt @@ -12,5 +12,5 @@ import javax.inject.Singleton interface LuckyNumberDao : BaseDao { @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") - fun load(studentId: Int, date: LocalDate): Flow + fun load(studentId: Int, date: LocalDate): Flow } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt index ef0ced3a5..173ce7e45 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt @@ -3,7 +3,8 @@ package io.github.wulkanowy.data.repositories.luckynumber import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.utils.networkBoundResource -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import java.time.LocalDate.now import javax.inject.Inject import javax.inject.Singleton @@ -28,11 +29,8 @@ class LuckyNumberRepository @Inject constructor( } ) - fun getNotNotifiedLuckyNumber(student: Student): Flow { - return local.getLuckyNumber(student, now()) - } + suspend fun getNotNotifiedLuckyNumber(student: Student) = + local.getLuckyNumber(student, now()).map { if (it?.isNotified == false) it else null }.first() - suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) { - local.updateLuckyNumber(luckyNumber) - } + suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = local.updateLuckyNumber(luckyNumber) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt index 28d37ed85..7a76503f2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.data.repositories.semester +import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider @@ -18,24 +19,33 @@ class SemesterRepository @Inject constructor( ) { suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { - local.getSemesters(student).let { semesters -> - semesters.filter { - !forceRefresh && when { - Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> semesters.firstOrNull { it.isCurrent }?.diaryId != 0 - refreshOnNoCurrent -> semesters.any { semester -> semester.isCurrent } - else -> true - } - } - }.ifEmpty { - val new = remote.getSemesters(student) - if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") - - val old = local.getSemesters(student) - local.deleteSemesters(old.uniqueSubtract(new)) - local.saveSemesters(new.uniqueSubtract(old)) + val semesters = local.getSemesters(student) + if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { + refreshSemesters(student) local.getSemesters(student) - } + } else semesters + } + + private fun isShouldFetch(student: Student, semesters: List, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean { + val isNoSemesters = semesters.isEmpty() + + val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + semesters.firstOrNull { it.isCurrent }?.diaryId == 0 + } else false + + val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } + + return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate + } + + private suspend fun refreshSemesters(student: Student) { + val new = remote.getSemesters(student) + if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") + + val old = local.getSemesters(student) + local.deleteSemesters(old.uniqueSubtract(new)) + local.saveSemesters(new.uniqueSubtract(old)) } suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt index cd25eea67..41fb61925 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/UpcomingLessonsChannel.kt @@ -26,6 +26,7 @@ class UpcomingLessonsChannel @Inject constructor( lockscreenVisibility = VISIBILITY_PUBLIC setShowBadge(false) enableVibration(false) + setSound(null, null) } ) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt index 9cb765ec4..7b9f5ab30 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt @@ -19,7 +19,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.waitForResult -import kotlinx.coroutines.flow.first import javax.inject.Inject import kotlin.random.Random @@ -33,7 +32,7 @@ class LuckyNumberWork @Inject constructor( override suspend fun doWork(student: Student, semester: Semester) { luckyNumberRepository.getLuckyNumber(student, true, preferencesRepository.isNotificationsEnable).waitForResult() - luckyNumberRepository.getNotNotifiedLuckyNumber(student).first()?.let { + luckyNumberRepository.getNotNotifiedLuckyNumber(student)?.let { notify(it) luckyNumberRepository.updateLuckyNumber(it.apply { isNotified = true }) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt index 30c4ccc23..ccebe94f0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryAdapter.kt @@ -21,7 +21,7 @@ class GradeSummaryAdapter @Inject constructor() : RecyclerView.Adapter() - override fun getItemCount() = items.size + 1 + override fun getItemCount() = items.size + if (items.isNotEmpty()) 1 else 0 override fun getItemViewType(position: Int) = when (position) { 0 -> ViewType.HEADER.id diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 9484d8b77..caa3cb84d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -45,16 +45,17 @@ class GradeSummaryPresenter @Inject constructor( Status.LOADING -> Timber.i("Loading grade summary started") Status.SUCCESS -> { Timber.i("Loading grade summary result: Success") + val items = createGradeSummaryItems(it.data!!) view?.run { - showEmpty(it.data!!.isEmpty()) - showContent(it.data.isNotEmpty()) + showEmpty(items.isEmpty()) + showContent(items.isNotEmpty()) showErrorView(false) - updateData(createGradeSummaryItems(it.data)) + updateData(items) } analytics.logEvent( "load_data", "type" to "grade_summary", - "items" to it.data!!.size + "items" to it.data.size ) } Status.ERROR -> { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 1d509e1bb..e58377058 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -66,7 +66,12 @@ class LoginRecoverPresenter @Inject constructor( showErrorView(false) showCaptcha(false) } - Status.SUCCESS -> view?.loadReCaptcha(siteKey = it.data!!.first, url = it.data.second) + Status.SUCCESS -> view?.run { + loadReCaptcha(url = it.data!!.first, siteKey = it.data.second) + showProgress(false) + showErrorView(false) + showCaptcha(true) + } Status.ERROR -> { Timber.i("Obtain captcha site key result: An exception occurred") errorHandler.dispatch(it.error!!) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index a8a15cec5..6f4a695d6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -120,15 +120,6 @@ class SettingsFragment : PreferenceFragmentCompat(), ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } - override fun showForceSyncDialog() { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.pref_services_dialog_force_sync_title) - .setMessage(R.string.pref_services_dialog_force_sync_summary) - .setPositiveButton(android.R.string.ok) { _, _ -> presenter.onForceSyncDialogSubmit() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() - } - override fun showFixSyncDialog() { AlertDialog.Builder(requireContext()) .setTitle(R.string.pref_notify_fix_sync_issues) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index f9e6731f9..3d1e063e2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -55,14 +55,6 @@ class SettingsPresenter @Inject constructor( } fun onSyncNowClicked() { - view?.showForceSyncDialog() - } - - fun onFixSyncIssuesClicked() { - view?.showFixSyncDialog() - } - - fun onForceSyncDialogSubmit() { view?.run { syncManager.startOneTimeSyncWorker().onEach { workInfo -> when (workInfo.state) { @@ -87,4 +79,8 @@ class SettingsPresenter @Inject constructor( }.launch("sync") } } + + fun onFixSyncIssuesClicked() { + view?.showFixSyncDialog() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt index 3786ba4b2..b647c0b76 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt @@ -18,6 +18,5 @@ interface SettingsView : BaseView { fun setSyncInProgress(inProgress: Boolean) - fun showForceSyncDialog() fun showFixSyncDialog() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index e410eeb95..8581b73c6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.lang.NullPointerException import java.time.LocalDate import java.time.LocalDate.now import java.time.LocalDate.of diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt index 736a42709..314994054 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Status import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filter @@ -69,22 +70,24 @@ inline fun networkBoundResource( fun flowWithResource(block: suspend () -> T) = flow { emit(Resource.loading()) - try { - emit(Resource.success(block())) + emit(try { + Resource.success(block()) } catch (e: Throwable) { - emit(Resource.error(e)) - } + Resource.error(e) + }) } fun flowWithResourceIn(block: suspend () -> Flow>) = flow { emit(Resource.loading()) try { - block().collect { - if (it.status != Status.LOADING) { // LOADING is already emitted - emit(it) + block() + .catch { emit(Resource.error(it)) } + .collect { + if (it.status != Status.LOADING) { // LOADING is already emitted + emit(it) + } } - } } catch (e: Throwable) { emit(Resource.error(e)) } 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 5cf0c5175..51c3c13ac 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,7 @@ -Wersja 0.20.0 -- naprawiliśmy obsługę wiadomości -- naprawiliśmy wyświetlanie oznaczenia klasy ucznia w menadżerze kont +Wersja 0.20.1 +- naprawiliśmy powiadomienie o szczęśliwym numerku +- naprawiliśmy przełączanie na nowy rok szkolny +- naprawiliśmy funkcję resetowania hasła +- dodaliśmy dziennik e-Skarżysko Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index f661b88c5..64618568a 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -10,6 +10,7 @@ Rawa Mazowiecka - Platforma vEdukacja Zduńska Wola - e-Urząd Sieradz - Portal oświatowy + Skarżysko-Kamienna - e-Skarżysko Łask - Platforma vEdukacja Powiat łaski - Platforma edukacyjna Powiat Krasnostawski - Platforma oświatowa @@ -36,6 +37,7 @@ https://vulcan.net.pl/ https://vulcan.net.pl/ https://vulcan.net.pl/ + https://vulcan.net.pl/ http://fakelog.tk/?standard @@ -48,6 +50,7 @@ rawamazowiecka zdunskawola sieradz + skarzyskokamienna lask powiatlaski powiatkrasnostawski diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt index 866c2af4d..5a2388ff7 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt @@ -63,7 +63,15 @@ class SemesterRepositoryTest { createSemesterEntity(0, 2, now().minusMonths(3), now()) ) + val goodSemesters = listOf( + createSemesterEntity(122, 1, now().minusMonths(6), now().minusMonths(3)), + createSemesterEntity(123, 2, now().minusMonths(3), now()) + ) + coEvery { semesterLocal.getSemesters(student) } returns badSemesters + coEvery { semesterRemote.getSemesters(student) } returns goodSemesters + coEvery { semesterLocal.deleteSemesters(any()) } just Runs + coEvery { semesterLocal.saveSemesters(any()) } just Runs val items = runBlocking { semesterRepository.getSemesters(student) } assertEquals(2, items.size) @@ -152,12 +160,23 @@ class SemesterRepositoryTest { @Test fun getSemesters_noCurrent_refreshOnNoCurrent() { - val semesters = listOf( + val semestersWithNoCurrent = listOf( createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) ) - coEvery { semesterLocal.getSemesters(student) } returns semesters + val newSemesters = listOf( + createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), + createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)), + + createSemesterEntity(2, 1, now().minusMonths(1), now().plusMonths(5)), + createSemesterEntity(2, 2, now().plusMonths(5), now().plusMonths(11)), + ) + + coEvery { semesterLocal.getSemesters(student) } returns semestersWithNoCurrent + coEvery { semesterRemote.getSemesters(student) } returns newSemesters + coEvery { semesterLocal.deleteSemesters(any()) } just Runs + coEvery { semesterLocal.saveSemesters(any()) } just Runs val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } assertEquals(2, items.size)