From 20dde6e89605495f1481d0bf588cba1f3dd00878 Mon Sep 17 00:00:00 2001 From: Michael <5672750+mibac138@users.noreply.github.com> Date: Sun, 27 Mar 2022 15:33:32 +0200 Subject: [PATCH] Resource refactor (#1589) --- .../java/io/github/wulkanowy/data/Resource.kt | 180 ++++++- .../wulkanowy/data/db/dao/StudentDao.kt | 4 + .../repositories/AdminMessageRepository.kt | 3 +- .../data/repositories/AttendanceRepository.kt | 2 + .../AttendanceSummaryRepository.kt | 7 +- .../CompletedLessonsRepository.kt | 2 + .../data/repositories/ConferenceRepository.kt | 7 +- .../data/repositories/ExamRepository.kt | 2 + .../data/repositories/GradeRepository.kt | 5 + .../repositories/GradeStatisticsRepository.kt | 9 +- .../data/repositories/HomeworkRepository.kt | 2 + .../repositories/LuckyNumberRepository.kt | 3 +- .../data/repositories/MessageRepository.kt | 14 +- .../repositories/MobileDeviceRepository.kt | 7 +- .../data/repositories/NoteRepository.kt | 2 + .../SchoolAnnouncementRepository.kt | 3 +- .../data/repositories/SchoolRepository.kt | 3 +- .../repositories/StudentInfoRepository.kt | 3 +- .../data/repositories/StudentRepository.kt | 9 + .../data/repositories/SubjectRepository.kt | 7 +- .../data/repositories/TeacherRepository.kt | 7 +- .../data/repositories/TimetableRepository.kt | 14 +- .../alarm/TimetableNotificationReceiver.kt | 13 +- .../sync/works/AttendanceSummaryWork.kt | 2 +- .../services/sync/works/AttendanceWork.kt | 2 +- .../sync/works/CompletedLessonWork.kt | 2 +- .../services/sync/works/ConferenceWork.kt | 2 +- .../wulkanowy/services/sync/works/ExamWork.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 3 +- .../services/sync/works/GradeWork.kt | 2 +- .../services/sync/works/HomeworkWork.kt | 2 +- .../services/sync/works/LuckyNumberWork.kt | 2 +- .../services/sync/works/MessageWork.kt | 2 +- .../wulkanowy/services/sync/works/NoteWork.kt | 2 +- .../sync/works/SchoolAnnouncementWork.kt | 2 +- .../services/sync/works/TeacherWork.kt | 3 +- .../services/sync/works/TimetableWork.kt | 2 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 43 +- .../about/contributor/ContributorPresenter.kt | 20 +- .../modules/about/license/LicensePresenter.kt | 26 +- .../ui/modules/account/AccountPresenter.kt | 25 +- .../accountdetails/AccountDetailsPresenter.kt | 110 ++-- .../accountedit/AccountEditPresenter.kt | 52 +- .../accountquick/AccountQuickPresenter.kt | 25 +- .../modules/attendance/AttendancePresenter.kt | 135 ++--- .../summary/AttendanceSummaryFragment.kt | 2 +- .../summary/AttendanceSummaryPresenter.kt | 102 ++-- .../summary/AttendanceSummaryView.kt | 2 +- .../modules/conference/ConferencePresenter.kt | 72 +-- .../ui/modules/dashboard/DashboardFragment.kt | 2 +- .../modules/dashboard/DashboardPresenter.kt | 389 +++++++------- .../debug/logviewer/LogViewerPresenter.kt | 52 +- .../ui/modules/exam/ExamPresenter.kt | 108 ++-- .../ui/modules/grade/GradeAverageProvider.kt | 33 +- .../ui/modules/grade/GradePresenter.kt | 46 +- .../grade/details/GradeDetailsDialog.kt | 1 + .../grade/details/GradeDetailsPresenter.kt | 132 ++--- .../statistics/GradeStatisticsPresenter.kt | 134 ++--- .../grade/summary/GradeSummaryPresenter.kt | 85 ++- .../ui/modules/homework/HomeworkPresenter.kt | 113 ++-- .../homework/add/HomeworkAddPresenter.kt | 31 +- .../details/HomeworkDetailsPresenter.kt | 49 +- .../login/advanced/LoginAdvancedPresenter.kt | 62 +-- .../modules/login/form/LoginFormPresenter.kt | 75 ++- .../login/recover/LoginRecoverPresenter.kt | 61 ++- .../LoginStudentSelectPresenter.kt | 35 +- .../login/symbol/LoginSymbolPresenter.kt | 24 +- .../luckynumber/LuckyNumberPresenter.kt | 71 ++- .../history/LuckyNumberHistoryPresenter.kt | 102 ++-- .../LuckyNumberWidgetConfigurePresenter.kt | 17 +- .../LuckyNumberWidgetProvider.kt | 20 +- .../ui/modules/main/MainPresenter.kt | 29 +- .../preview/MessagePreviewPresenter.kt | 104 ++-- .../message/send/SendMessagePresenter.kt | 117 +++-- .../message/tab/MessageTabPresenter.kt | 116 ++-- .../mobiledevice/MobileDevicePresenter.kt | 94 ++-- .../token/MobileDeviceTokenPresenter.kt | 43 +- .../ui/modules/note/NotePresenter.kt | 84 ++- .../school/SchoolPresenter.kt | 53 +- .../teacher/TeacherPresenter.kt | 65 ++- .../SchoolAnnouncementPresenter.kt | 70 +-- .../studentinfo/StudentInfoPresenter.kt | 72 ++- .../modules/timetable/TimetablePresenter.kt | 80 ++- .../additional/AdditionalLessonsPresenter.kt | 74 ++- .../completed/CompletedLessonsPresenter.kt | 88 ++-- .../TimetableWidgetConfigurePresenter.kt | 17 +- .../timetablewidget/TimetableWidgetFactory.kt | 5 +- .../io/github/wulkanowy/utils/FlowUtils.kt | 96 ---- .../ResourceTest.kt} | 8 +- .../repositories/AttendanceRepositoryTest.kt | 23 +- .../CompletedLessonsRepositoryTest.kt | 23 +- .../data/repositories/ExamRemoteTest.kt | 23 +- .../data/repositories/GradeRepositoryTest.kt | 28 +- .../GradeStatisticsRepositoryTest.kt | 16 +- .../repositories/LuckyNumberRemoteTest.kt | 22 +- .../repositories/MessageRepositoryTest.kt | 19 +- .../MobileDeviceRepositoryTest.kt | 23 +- .../repositories/TimetableRepositoryTest.kt | 15 +- .../modules/grade/GradeAverageProviderTest.kt | 494 +++++++++++++----- .../github/wulkanowy/utils/ResourceUtils.kt | 14 + gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 269 ++++++---- 102 files changed, 2419 insertions(+), 2361 deletions(-) delete mode 100644 app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt rename app/src/test/java/io/github/wulkanowy/{utils/FlowUtilsKtTest.kt => data/ResourceTest.kt} (96%) create mode 100644 app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt index 406440c8..44f8a1b4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -1,23 +1,173 @@ package io.github.wulkanowy.data -data class Resource(val status: Status, val data: T?, val error: Throwable?) { - companion object { - fun success(data: T?): Resource { - return Resource(Status.SUCCESS, data, null) - } +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber - fun error(error: Throwable?, data: T? = null): Resource { - return Resource(Status.ERROR, data, error) - } +sealed class Resource { - fun loading(data: T? = null): Resource { - return Resource(Status.LOADING, data, null) - } + open class Loading : Resource() + + data class Intermediate(val data: T) : Loading() + + data class Success(val data: T) : Resource() + + data class Error(val error: Throwable) : Resource() +} + +val Resource.dataOrNull: T? + get() = when (this) { + is Resource.Success -> this.data + is Resource.Intermediate -> this.data + is Resource.Loading -> null + is Resource.Error -> null + } + +val Resource.errorOrNull: Throwable? + get() = when (this) { + is Resource.Error -> this.error + else -> null + } + +fun resourceFlow(block: suspend () -> T) = flow { + emit(Resource.Loading()) + emit(Resource.Success(block())) +}.catch { emit(Resource.Error(it)) } + +fun flatResourceFlow(block: suspend () -> Flow>) = flow { + emit(Resource.Loading()) + emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading }) +}.catch { emit(Resource.Error(it)) } + +fun Resource.mapData(block: (T) -> U) = when (this) { + is Resource.Success -> Resource.Success(block(this.data)) + is Resource.Intermediate -> Resource.Intermediate(block(this.data)) + is Resource.Loading -> Resource.Loading() + is Resource.Error -> Resource.Error(this.error) +} + +fun Flow>.logResourceStatus(name: String, showData: Boolean = false) = onEach { + val description = when (it) { + is Resource.Loading -> "started" + is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" + is Resource.Error -> "exception occurred: ${it.error}" + } + Timber.i("$name: $description") +} + +fun Flow>.mapResourceData(block: (T) -> U) = map { + it.mapData(block) +} + +fun Flow>.onResourceData(block: suspend (T) -> Unit) = onEach { + when (it) { + is Resource.Success -> block(it.data) + is Resource.Intermediate -> block(it.data) + is Resource.Error, + is Resource.Loading -> Unit } } -enum class Status { - LOADING, - SUCCESS, - ERROR +fun Flow>.onResourceLoading(block: suspend () -> Unit) = onEach { + if (it is Resource.Loading) { + block() + } +} + +fun Flow>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Intermediate) { + block(it.data) + } +} + +fun Flow>.onResourceSuccess(block: suspend (T) -> Unit) = onEach { + if (it is Resource.Success) { + block(it.data) + } +} + +fun Flow>.onResourceError(block: (Throwable) -> Unit) = onEach { + if (it is Resource.Error) { + block(it.error) + } +} + +fun Flow>.onResourceNotLoading(block: () -> Unit) = onEach { + if (it !is Resource.Loading) { + block() + } +} + +suspend fun Flow>.toFirstResult() = filter { it !is Resource.Loading }.first() + +suspend fun Flow>.waitForResult() = takeWhile { it is Resource.Loading }.collect() + +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + showSavedOnLoading: Boolean = true, + crossinline isResultEmpty: (ResultType) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline filterResult: (ResultType) -> ResultType = { it } +) = flow { + emit(Resource.Loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + val filteredResult = filterResult(data) + + if (showSavedOnLoading && !isResultEmpty(filteredResult)) { + emit(Resource.Intermediate(filteredResult)) + } + + try { + val newData = fetch(data) + mutex.withLock { saveFetchResult(query().first(), newData) } + query().map { Resource.Success(filterResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.Error(throwable) } + } + } else { + query().map { Resource.Success(filterResult(it)) } + }) +} + +@JvmName("networkBoundResourceWithMap") +inline fun networkBoundResource( + mutex: Mutex = Mutex(), + showSavedOnLoading: Boolean = true, + crossinline isResultEmpty: (T) -> Boolean, + crossinline query: () -> Flow, + crossinline fetch: suspend (ResultType) -> RequestType, + crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, + crossinline onFetchFailed: (Throwable) -> Unit = { }, + crossinline shouldFetch: (ResultType) -> Boolean = { true }, + crossinline mapResult: (ResultType) -> T +) = flow { + emit(Resource.Loading()) + + val data = query().first() + emitAll(if (shouldFetch(data)) { + val mappedResult = mapResult(data) + + if (showSavedOnLoading && !isResultEmpty(mappedResult)) { + emit(Resource.Intermediate(mappedResult)) + } + try { + val newData = fetch(data) + mutex.withLock { saveFetchResult(query().first(), newData) } + query().map { Resource.Success(mapResult(it)) } + } catch (throwable: Throwable) { + onFetchFailed(throwable) + query().map { Resource.Error(throwable) } + } + } else { + query().map { Resource.Success(mapResult(it)) } + }) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 853a7cb7..87b3e0b3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -33,6 +33,10 @@ abstract class StudentDao { @Query("SELECT * FROM Students") abstract suspend fun loadStudentsWithSemesters(): List + @Transaction + @Query("SELECT * FROM Students WHERE id = :id") + abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters? + @Query("UPDATE Students SET is_current = 1 WHERE id = :id") abstract suspend fun updateCurrent(id: Long) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt index e455411e..c9655b72 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AdminMessageRepository.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.data.repositories import io.github.wulkanowy.data.api.AdminMessageService import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -19,6 +19,7 @@ class AdminMessageRepository @Inject constructor( suspend fun getAdminMessages(student: Student) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, query = { adminMessageDao.loadAll() }, fetch = { adminMessageService.getAdminMessages() }, shouldFetch = { true }, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index 7184f557..9aa6562a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.utils.* @@ -36,6 +37,7 @@ class AttendanceRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt index 0857475f..8e070913 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -28,6 +32,7 @@ class AttendanceSummaryRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index 2055f3f4..8f393cad 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex @@ -30,6 +31,7 @@ class CompletedLessonsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt index 6af24d73..83204cab 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -5,8 +5,12 @@ import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.Instant @@ -32,6 +36,7 @@ class ConferenceRepository @Inject constructor( startDate: Instant = Instant.EPOCH, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index c655c800..faa80b93 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -33,6 +34,7 @@ class ExamRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index f4087a88..f5f895d8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -36,6 +37,10 @@ class GradeRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { + //When details is empty and summary is not, app will not use summary cache - edge case + it.first.isEmpty() + }, shouldFetch = { (details, summaries) -> val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index 4d26c312..9fa06c49 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -11,8 +11,12 @@ import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import java.util.* import javax.inject.Inject @@ -42,6 +46,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = partialMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(partialCacheKey, semester) @@ -86,6 +91,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = semesterMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(semesterCacheKey, semester) @@ -143,6 +149,7 @@ class GradeStatisticsRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = pointsMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 900d9a68..f564824d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.sync.Mutex @@ -32,6 +33,7 @@ class HomeworkRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, semester, start, end) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 41e824e5..87e8410f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex @@ -29,6 +29,7 @@ class LuckyNumberRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { it == null || forceRefresh }, query = { luckyNumberDb.load(student.studentId, now()) }, fetch = { 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 224c69bd..8d6fd772 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 @@ -7,15 +7,12 @@ import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Folder @@ -23,7 +20,6 @@ import io.github.wulkanowy.sdk.pojo.SentMessage import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -59,6 +55,7 @@ class MessageRepository @Inject constructor( notify: Boolean = false, ): Flow>> = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, student, folder) @@ -106,8 +103,9 @@ class MessageRepository @Inject constructor( message: Message, markAsRead: Boolean = false, ): Flow> = networkBoundResource( + isResultEmpty = { it == null }, shouldFetch = { - checkNotNull(it, { "This message no longer exist!" }) + checkNotNull(it) { "This message no longer exist!" } Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") it.message.unread || it.message.content.isEmpty() }, @@ -123,7 +121,7 @@ class MessageRepository @Inject constructor( } }, saveFetchResult = { old, (downloadedMessage, attachments) -> - checkNotNull(old, { "Fetched message no longer exist!" }) + checkNotNull(old) { "Fetched message no longer exist!" } messagesDb.updateAll(listOf(old.message.apply { id = old.message.id unread = !markAsRead diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt index f825c36d..eda40cac 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -6,9 +6,13 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -30,6 +34,7 @@ class MobileDeviceRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index 19ad8f03..e5d7bc5c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.Flow @@ -30,6 +31,7 @@ class NoteRepository @Inject constructor( notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( getRefreshKey(cacheKey, semester) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt index b6724ed3..cf7ac86c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolAnnouncementRepository.kt @@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex @@ -31,6 +31,7 @@ class SchoolAnnouncementRepository @Inject constructor( forceRefresh: Boolean, notify: Boolean = false ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt index 880a6a74..7972ed08 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -30,6 +30,7 @@ class SchoolRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed( key = getRefreshKey(cacheKey, student) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt index 1fa91dd4..efc82a77 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentInfoRepository.kt @@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.StudentInfoDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init -import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -25,6 +25,7 @@ class StudentInfoRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it == null }, shouldFetch = { it == null || forceRefresh }, query = { studentInfoDao.loadStudentInfo(student.studentId) }, fetch = { 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 570f8bdb..f006b7d2 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 @@ -73,6 +73,15 @@ class StudentRepository @Inject constructor( } } + suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) = + studentDb.loadStudentWithSemestersById(id)?.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + student.password = withContext(dispatchers.io) { + decrypt(student.password) + } + } + } + suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt index b9bca028..3926122b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -27,6 +31,7 @@ class SubjectRepository @Inject constructor( forceRefresh: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt index 6b615c7a..acd71e1f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -4,8 +4,12 @@ import io.github.wulkanowy.data.db.dao.TeacherDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.* +import io.github.wulkanowy.utils.AutoRefreshHelper +import io.github.wulkanowy.utils.getRefreshKey +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.sync.Mutex import javax.inject.Inject import javax.inject.Singleton @@ -27,6 +31,7 @@ class TeacherRepository @Inject constructor( forceRefresh: Boolean, ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { it.isEmpty() }, shouldFetch = { val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) it.isEmpty() || forceRefresh || isExpired diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 7534640c..3145c2a2 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper @@ -30,6 +31,10 @@ class TimetableRepository @Inject constructor( private val cacheKey = "timetable" + enum class TimetableType { + NORMAL, ADDITIONAL + } + fun getTimetable( student: Student, semester: Semester, @@ -37,9 +42,16 @@ class TimetableRepository @Inject constructor( end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false, - notify: Boolean = false + notify: Boolean = false, + timetableType: TimetableType = TimetableType.NORMAL ) = networkBoundResource( mutex = saveFetchResultMutex, + isResultEmpty = { + when (timetableType) { + TimetableType.NORMAL -> it.lessons.isEmpty() + TimetableType.ADDITIONAL -> it.additional.isEmpty() + } + }, shouldFetch = { (timetable, additional, headers) -> val refreshKey = getRefreshKey(cacheKey, semester, start, end) val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index c3ff1838..01a583e1 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -10,19 +10,18 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.PendingIntentCompat -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCompatColor import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -59,7 +58,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Timber.d("Receiving intent... ${intent.toUri(0)}") - flowWithResource { + resourceFlow { val showStudentName = !studentRepository.isOneUniqueStudent() val student = studentRepository.getCurrentStudent(false) val studentId = intent.getIntExtra(STUDENT_ID, 0) @@ -69,9 +68,9 @@ class TimetableNotificationReceiver : BroadcastReceiver() { } else { Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") } - }.onEach { - if (it.status == Status.ERROR) Timber.e(it.error!!) - }.launchIn(GlobalScope) + } + .onResourceError { Timber.e(it) } + .launchIn(GlobalScope) } private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt index 84b7017b..55ce7e90 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult import javax.inject.Inject class AttendanceSummaryWork @Inject constructor( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index 9abf43e0..657f6963 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.AttendanceRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index c6ada944..f898aa04 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.CompletedLessonsRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.waitForResult import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt index becd7466..c85c0043 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ConferenceWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index 39579dc8..7071bce2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewExamNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 2e915199..ac35bc9a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -3,7 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.GradeStatisticsRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject class GradeStatisticsWork @Inject constructor( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index dd49f143..ba21b860 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewGradeNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index 1385191b..4cfe27d0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject 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 f223a854..668b1b6b 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 @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewLuckyNumberNotification -import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject class LuckyNumberWork @Inject constructor( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index 5bf326c7..26fac1a2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -4,8 +4,8 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewMessageNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt index d66c3d66..df6e2b06 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.NoteRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewNoteNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt index 9cee5902..805ceb3e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/SchoolAnnouncementWork.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt index 751fb6cc..e7c72bf0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt @@ -3,7 +3,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TeacherRepository -import io.github.wulkanowy.utils.waitForResult +import io.github.wulkanowy.data.waitForResult + import javax.inject.Inject class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 575f9b96..29b1f13c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.waitForResult import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 5cd5d010..15c069f5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,17 +1,10 @@ package io.github.wulkanowy.ui.base -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber open class BasePresenter( @@ -37,28 +30,28 @@ open class BasePresenter( } fun onExpiredLoginSelected() { - flowWithResource { - val student = studentRepository.getCurrentStudent(false) - studentRepository.logoutStudent(student) + Timber.i("Attempt to switch the student after the session expires") - val students = studentRepository.getSavedStudents(false) - if (students.isNotEmpty()) { - Timber.i("Switching current student") - studentRepository.switchStudent(students[0]) + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent(false) + studentRepository.logoutStudent(student) + + val students = studentRepository.getSavedStudents(false) + if (students.isNotEmpty()) { + Timber.i("Switching current student") + studentRepository.switchStudent(students[0]) + } } - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to switch the student after the session expires") - Status.SUCCESS -> { + .onFailure { + Timber.i("Switch student result: An exception occurred") + errorHandler.dispatch(it) + } + .onSuccess { Timber.i("Switch student result: Open login view") view?.openClearLoginView() } - Status.ERROR -> { - Timber.i("Switch student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("expired") + } } fun Flow.launch(individualJobTag: String = "load"): Job { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index ef4b540e..126bb2b4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -1,13 +1,11 @@ package io.github.wulkanowy.ui.modules.about.contributor -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.repositories.AppCreatorRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import javax.inject.Inject class ContributorPresenter @Inject constructor( @@ -31,15 +29,11 @@ class ContributorPresenter @Inject constructor( } private fun loadData() { - flowWithResource { appCreatorRepository.getAppCreators() }.onEach { - when (it.status) { - Status.LOADING -> view?.showProgress(true) - Status.SUCCESS -> view?.run { - showProgress(false) - updateData(it.data!!) - } - Status.ERROR -> errorHandler.dispatch(it.error!!) - } - }.launch() + resourceFlow { appCreatorRepository.getAppCreators() } + .onResourceLoading { view?.showProgress(true) } + .onResourceSuccess { view?.updateData(it) } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError { errorHandler.dispatch(it) } + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt index 5368cc19..5aa12a57 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt @@ -1,16 +1,12 @@ package io.github.wulkanowy.ui.modules.about.license import com.mikepenz.aboutlibraries.entity.Library -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber import javax.inject.Inject class LicensePresenter @Inject constructor( @@ -30,18 +26,16 @@ class LicensePresenter @Inject constructor( } private fun loadData() { - flowWithResource { - withContext(dispatchers.io) { - view?.appLibraries.orEmpty() + presenterScope.launch { + runCatching { + withContext(dispatchers.io) { + view?.appLibraries.orEmpty() + } } - }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("License data load started") - Status.SUCCESS -> view?.updateData(it.data!!) - Status.ERROR -> errorHandler.dispatch(it.error!!) - } - }.afterLoading { + .onFailure { errorHandler.dispatch(it) } + .onSuccess { view?.updateData(it) } + view?.showProgress(false) - }.launch() + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 7fe77ca7..77c1ffe6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,12 +1,13 @@ package io.github.wulkanowy.ui.modules.account -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -32,20 +33,10 @@ class AccountPresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading account data started") - Status.SUCCESS -> { - Timber.i("Loading account result: Success") - view?.updateData(createAccountItems(it.data!!)) - } - Status.ERROR -> { - Timber.i("Loading account result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - } + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load account data") + .onResourceSuccess { view?.updateData(createAccountItems(it)) } + .onResourceError(errorHandler::dispatch) .launch("load") } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt index 1f44cbbc..5d68ff2e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.account.accountdetails -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository @@ -9,10 +8,6 @@ import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -51,40 +46,25 @@ class AccountDetailsPresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents() } - .map { studentWithSemesters -> - Resource( - data = studentWithSemesters.data?.single { it.student.id == studentId }, - status = studentWithSemesters.status, - error = studentWithSemesters.error - ) - } - .onEach { - when (it.status) { - Status.LOADING -> { - view?.run { - showProgress(true) - showContent(false) - } - Timber.i("Loading account details view started") - } - Status.SUCCESS -> { - Timber.i("Loading account details view result: Success") - studentWithSemesters = it.data - view?.run { - showAccountData(studentWithSemesters!!.student) - enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent) - showContent(true) - showErrorView(false) - } - } - Status.ERROR -> { - Timber.i("Loading account details view result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) } + .logResourceStatus("loading account details view") + .onResourceLoading { + view?.run { + showProgress(true) + showContent(false) } } - .afterLoading { view?.showProgress(false) } + .onResourceSuccess { + studentWithSemesters = it + view?.run { + showAccountData(studentWithSemesters!!.student) + enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent) + showContent(true) + showErrorView(false) + } + } + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError(errorHandler::dispatch) .launch() } @@ -105,22 +85,12 @@ class AccountDetailsPresenter @Inject constructor( Timber.i("Select student ${studentWithSemesters!!.student.id}") - flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student") - Status.SUCCESS -> { - Timber.i("Change a student result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { - view?.popViewToMain() - }.launch("switch") + resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popViewToMain() } + .onResourceError(errorHandler::dispatch) + .launch("switch") } fun onRemoveSelected() { @@ -131,7 +101,7 @@ class AccountDetailsPresenter @Inject constructor( fun onLogoutConfirm() { if (studentWithSemesters == null) return - flowWithResource { + resourceFlow { val studentToLogout = studentWithSemesters!!.student studentRepository.logoutStudent(studentToLogout) @@ -141,13 +111,13 @@ class AccountDetailsPresenter @Inject constructor( studentRepository.switchStudent(students[0]) } - return@flowWithResource students - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to logout user") - Status.SUCCESS -> view?.run { + students + } + .logResourceStatus("logout user") + .onResourceSuccess { + view?.run { when { - it.data!!.isEmpty() -> { + it.isEmpty() -> { Timber.i("Logout result: Open login view") syncManager.stopSyncWorker() openClearLoginView() @@ -162,18 +132,16 @@ class AccountDetailsPresenter @Inject constructor( } } } - Status.ERROR -> { - Timber.i("Logout result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceNotLoading { + if (studentWithSemesters?.student?.isCurrent == true) { + view?.popViewToMain() + } else { + view?.popViewToAccounts() } } - }.afterLoading { - if (studentWithSemesters?.student?.isCurrent == true) { - view?.popViewToMain() - } else { - view?.popViewToAccounts() - } - }.launch("logout") + .onResourceError(errorHandler::dispatch) + .launch("logout") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt index 67ecdb5f..c401158e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.account.accountedit -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -38,43 +35,26 @@ class AccountEditPresenter @Inject constructor( } private fun loadData() { - flowWithResource { - studentRepository.getStudentById(student.id, false).avatarColor - }.onEach { resource -> - when (resource.status) { - Status.LOADING -> Timber.i("Attempt to load student") - Status.SUCCESS -> { - view?.updateSelectedColorData(resource.data?.toInt()!!) - Timber.i("Attempt to load student: Success") - } - Status.ERROR -> { - Timber.i("Attempt to load student: An exception occurred") - errorHandler.dispatch(resource.error!!) - } - } - }.launch("load_data") + resourceFlow { studentRepository.getStudentById(student.id, false).avatarColor } + .logResourceStatus("load student") + .onResourceSuccess { view?.updateSelectedColorData(it.toInt()) } + .onResourceError(errorHandler::dispatch) + .launch("load_data") } fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) { - flowWithResource { - val studentNick = - StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong()) - .apply { id = student.id } + resourceFlow { + val studentNick = StudentNickAndAvatar( + nick = nick.trim(), + avatarColor = avatarColor.toLong() + ).apply { id = student.id } + studentRepository.updateStudentNickAndAvatar(studentNick) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student nick and avatar") - Status.SUCCESS -> { - Timber.i("Change a student nick and avatar result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student nick and avatar result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } } - .afterLoading { view?.popView() } + .logResourceStatus("change student nick and avatar") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) .launch("update_student") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt index 39d8fce2..32c07f80 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt @@ -1,14 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountquick -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.account.AccountItem -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -43,21 +40,11 @@ class AccountQuickPresenter @Inject constructor( return } - flowWithResource { studentRepository.switchStudent(studentWithSemesters) } - .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to change a student") - Status.SUCCESS -> { - Timber.i("Change a student result: Success") - view?.recreateMainView() - } - Status.ERROR -> { - Timber.i("Change a student result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - } - .afterLoading { view?.popView() } + resourceFlow { studentRepository.switchStudent(studentWithSemesters) } + .logResourceStatus("change student") + .onResourceSuccess { view?.recreateMainView() } + .onResourceNotLoading { view?.popView() } + .onResourceError(errorHandler::dispatch) .launch("switch") } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 54d29bcf..7fcbd002 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -9,18 +9,7 @@ import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isExcusableOrNotExcused -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -213,93 +202,77 @@ class AttendancePresenter @Inject constructor( var isParent = false - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() isParent = student.isParent val semester = semesterRepository.getCurrentSemester(student) attendanceRepository.getAttendance( - student, - semester, - currentDate, - currentDate, - forceRefresh + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> { - view?.showExcuseButton(false) - if (!it.data.isNullOrEmpty()) { - val filteredAttendance = if (prefRepository.isShowPresent) { - it.data - } else { - it.data.filter { item -> !item.presence } - } - - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showErrorView(false) - showEmpty(filteredAttendance.isEmpty()) - showContent(filteredAttendance.isNotEmpty()) - updateData(filteredAttendance.sortedBy { item -> item.number }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading attendance result: Success") - val filteredAttendance = if (prefRepository.isShowPresent) { - it.data.orEmpty() - } else { - it.data?.filter { item -> !item.presence }.orEmpty() - } - - isVulcanExcusedFunctionEnabled = - filteredAttendance.any { item -> item.excusable } - - view?.apply { - updateData(filteredAttendance.sortedBy { item -> item.number }) - showEmpty(filteredAttendance.isEmpty()) - showErrorView(false) - showContent(filteredAttendance.isNotEmpty()) - val anyExcusables = filteredAttendance.any { it.isExcusableOrNotExcused } - showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled)) - } - analytics.logEvent( - "load_data", - "type" to "attendance", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading attendance result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance") + .onResourceLoading { + view?.showExcuseButton(false) + } + .mapResourceData { + if (prefRepository.isShowPresent) { + it + } else { + it.filter { item -> !item.presence } + }.sortedBy { item -> item.number } + } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable } + val anyExcusables = it.any { it.isExcusableOrNotExcused } + view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled)) + + analytics.logEvent( + "load_data", + "type" to "attendance", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun excuseAbsence(reason: String?, toExcuseList: List) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + when (it) { + is Resource.Loading -> view?.run { Timber.i("Excusing absence started") showProgress(true) showContent(false) showExcuseButton(false) } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Excusing for absence result: Success") analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) attendanceToExcuseList.clear() @@ -311,9 +284,9 @@ class AttendancePresenter @Inject constructor( } loadData(forceRefresh = true) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Excusing for absence result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) loadData() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt index dd1644a9..e750b8d5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -74,7 +74,7 @@ class AttendanceSummaryFragment : binding.attendanceSummarySubjectsContainer.elevation = requireContext().dpToPx(1f) } - override fun updateSubjects(data: ArrayList) { + override fun updateSubjects(data: Collection) { with(subjectsAdapter) { clear() addAll(data) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 8b603837..28199917 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.attendance.summary -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository @@ -10,9 +10,6 @@ import io.github.wulkanowy.data.repositories.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.Month import javax.inject.Inject @@ -75,11 +72,9 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { - Timber.i("Loading attendance summary data started") - currentSubjectId = subjectId - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) @@ -89,47 +84,37 @@ class AttendanceSummaryPresenter @Inject constructor( subjectId = subjectId, forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - showErrorView(false) - updateDataSet(sortItems(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading attendance summary result: Success") - view?.apply { - showErrorView(false) - showEmpty(it.data!!.isEmpty()) - showContent(it.data.isNotEmpty()) - updateDataSet(sortItems(it.data)) - } - analytics.logEvent( - "load_data", - "type" to "attendance_summary", - "items" to it.data!!.size, - "item_id" to subjectId - ) - } - Status.ERROR -> { - Timber.i("Loading attendance summary result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance summary") + .mapResourceData(this::sortItems) + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateDataSet(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "attendance_summary", + "items" to it.size, + "item_id" to subjectId + ) } - }.launch() + .onResourceNotLoading { + view?.run { + showProgress(false) + showRefresh(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun sortItems(items: List) = items.sortedByDescending { item -> @@ -148,27 +133,20 @@ class AttendanceSummaryPresenter @Inject constructor( } private fun loadSubjects() { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) subjectRepository.getSubjects(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading attendance summary subjects started") - Status.SUCCESS -> { - subjects = it.data!! - - Timber.i("Loading attendance summary subjects result: Success") - view?.run { - view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name })) - showSubjects(true) - } - } - Status.ERROR -> { - Timber.i("Loading attendance summary subjects result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load attendance summary subjects") + .onResourceData { + subjects = it + view?.run { + view?.updateSubjects(it.map { subject -> subject.name }.toList()) + showSubjects(true) } } - }.launch("subjects") + .onResourceError(errorHandler::dispatch) + .launch("subjects") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt index 66f370c5..99192f18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt @@ -25,7 +25,7 @@ interface AttendanceSummaryView : BaseView { fun updateDataSet(data: List) - fun updateSubjects(data: ArrayList) + fun updateSubjects(data: Collection) fun showSubjects(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt index dab170da..f5364893 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.conference -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.repositories.ConferenceRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,9 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -64,50 +61,39 @@ class ConferencePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading conference data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) conferenceRepository.getConferences(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedByDescending { conference -> conference.date }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading conference result: Success") - view?.run { - updateData(it.data!!.sortedByDescending { conference -> conference.date }) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "conferences", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading conference result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load conference data") + .mapResourceData { it.sortedByDescending { conference -> conference.date } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "conferences", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } } 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 88c281ec..65832bdb 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 @@ -204,4 +204,4 @@ class DashboardFragment : BaseFragment(R.layout.fragme presenter.onDetachView() super.onDestroyView() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index cad8d112..c33955bc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.dashboard -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student @@ -10,7 +9,6 @@ import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.calculatePercentage -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.nextOrSameSchoolDay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -104,7 +102,7 @@ class DashboardPresenter @Inject constructor( forceRefresh: Boolean ) = dashboardTilesToLoad.filter { newItemToLoad -> dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh - || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE + || newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE } private fun removeUnselectedTiles(tilesToLoad: List) { @@ -225,27 +223,26 @@ class DashboardPresenter @Inject constructor( val semester = semesterRepository.getCurrentSemester(student) val selectedTiles = preferencesRepository.selectedDashboardTiles + val flowSuccess = flowOf(Resource.Success(null)) val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) - .map { - if (it.data == null) { - it.copy(data = LuckyNumber(0, LocalDate.now(), 0)) - } else it + .mapResourceData { + it ?: LuckyNumber(0, LocalDate.now(), 0) } - .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null) + .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess val messageFLow = messageRepository.getMessages( student = student, semester = semester, folder = MessageFolder.RECEIVED, forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null) + ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary( student = student, semester = semester, subjectId = -1, forceRefresh = forceRefresh - ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null) + ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess emitAll( combine( @@ -253,17 +250,13 @@ class DashboardPresenter @Inject constructor( messageFLow, attendanceFlow ) { luckyNumberResource, messageResource, attendanceResource -> - val error = - luckyNumberResource?.error ?: messageResource?.error - ?: attendanceResource?.error - error?.let { throw it } + val resList = listOf(luckyNumberResource, messageResource, attendanceResource) + resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it } + val isLoading = resList.any { it is Resource.Loading } - val luckyNumber = luckyNumberResource?.data?.luckyNumber - val messageCount = messageResource?.data?.count { it.unread } - val attendancePercentage = attendanceResource?.data?.calculatePercentage() - - val isLoading = - luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING + val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber + val messageCount = messageResource.dataOrNull?.count { it.unread } + val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage() DashboardItem.HorizontalGroup( isLoading = isLoading, @@ -300,68 +293,65 @@ class DashboardPresenter @Inject constructor( } private fun loadGrades(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) gradeRepository.getGrades(student, semester, forceRefresh) - }.map { originalResource -> - val filteredSubjectWithGrades = originalResource.data?.first - .orEmpty() - .filter { it.date >= LocalDate.now().minusDays(7) } - .groupBy { it.subject } - .mapValues { entry -> - entry.value - .take(5) - .sortedByDescending { it.date } - } - .toList() - .sortedByDescending { (_, grades) -> grades[0].date } - .toMap() + } + .mapResourceData { (details, _) -> + val filteredSubjectWithGrades = details + .filter { it.date >= LocalDate.now().minusDays(7) } + .groupBy { it.subject } + .mapValues { entry -> + entry.value + .take(5) + .sortedByDescending { it.date } + } + .toList() + .sortedByDescending { (_, grades) -> grades[0].date } + .toMap() - Resource( - status = originalResource.status, - data = filteredSubjectWithGrades.takeIf { originalResource.data != null }, - error = originalResource.error - ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard grades data started") - if (forceRefresh) return@onEach + filteredSubjectWithGrades + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard grades data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Grades( + subjectWithGrades = it.dataOrNull, + gradeTheme = preferencesRepository.gradeColorTheme, + isLoading = true + ), forceRefresh + ) - updateData( - DashboardItem.Grades( - subjectWithGrades = it.data, - gradeTheme = preferencesRepository.gradeColorTheme, - isLoading = true - ), forceRefresh - ) - - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.GRADES + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.GRADES + } + } + is Resource.Success -> { + Timber.i("Loading dashboard grades result: Success") + updateData( + DashboardItem.Grades( + subjectWithGrades = it.data, + gradeTheme = preferencesRepository.gradeColorTheme + ), + forceRefresh + ) + } + is Resource.Error -> { + Timber.i("Loading dashboard grades result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Grades(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard grades result: Success") - updateData( - DashboardItem.Grades( - subjectWithGrades = it.data, - gradeTheme = preferencesRepository.gradeColorTheme - ), - forceRefresh - ) - } - Status.ERROR -> { - Timber.i("Loading dashboard grades result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Grades(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_grades", forceRefresh) } private fun loadLessons(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) val date = LocalDate.now().nextOrSameSchoolDay @@ -372,40 +362,41 @@ class DashboardPresenter @Inject constructor( end = date.plusDays(1), forceRefresh = forceRefresh ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard lessons data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Lessons(it.dataOrNull, isLoading = true), + forceRefresh + ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard lessons data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Lessons(it.data, isLoading = true), - forceRefresh - ) - - if (!it.data?.lessons.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.LESSONS + if (!it.dataOrNull?.lessons.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.LESSONS + } + } + is Resource.Success -> { + Timber.i("Loading dashboard lessons result: Success") + updateData( + DashboardItem.Lessons(it.data), forceRefresh + ) + } + is Resource.Error -> { + Timber.i("Loading dashboard lessons result: An exception occurred") + errorHandler.dispatch(it.error) + updateData( + DashboardItem.Lessons(error = it.error), forceRefresh + ) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard lessons result: Success") - updateData( - DashboardItem.Lessons(it.data), forceRefresh - ) - } - Status.ERROR -> { - Timber.i("Loading dashboard lessons result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData( - DashboardItem.Lessons(error = it.error), forceRefresh - ) - } } - }.launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh) } private fun loadHomework(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) val date = LocalDate.now().nextOrSameSchoolDay @@ -416,73 +407,79 @@ class DashboardPresenter @Inject constructor( end = date, forceRefresh = forceRefresh ) - }.map { homeworkResource -> - val currentDate = LocalDate.now() + } + .mapResourceData { homework -> + val currentDate = LocalDate.now() - val filteredHomework = homeworkResource.data - ?.filter { (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone } - ?.sortedBy { it.date } + val filteredHomework = homework.filter { + (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone + }.sortedBy { it.date } - homeworkResource.copy(data = filteredHomework) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard homework data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Homework(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + filteredHomework + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard homework data started") + if (forceRefresh) return@onEach + val data = it.dataOrNull.orEmpty() + updateData( + DashboardItem.Homework(data, isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.HOMEWORK + if (data.isNotEmpty()) { + firstLoadedItemList += DashboardItem.Type.HOMEWORK + } + } + is Resource.Success -> { + Timber.i("Loading dashboard homework result: Success") + updateData(DashboardItem.Homework(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard homework result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Homework(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard homework result: Success") - updateData(DashboardItem.Homework(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard homework result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Homework(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_homework", forceRefresh) } private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard announcements data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard announcements data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS + } + } + is Resource.Success -> { + Timber.i("Loading dashboard announcements result: Success") + updateData(DashboardItem.Announcements(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard announcements result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Announcements(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard announcements result: Success") - updateData(DashboardItem.Announcements(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard announcements result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Announcements(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh) } private fun loadExams(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) examRepository.getExams( @@ -493,40 +490,37 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh ) } - .map { examResource -> - val sortedExams = examResource.data?.sortedBy { it.date } - - examResource.copy(data = sortedExams) - } + .mapResourceData { exams -> exams.sortedBy { exam -> exam.date } } .onEach { - when (it.status) { - Status.LOADING -> { + when (it) { + is Resource.Loading -> { Timber.i("Loading dashboard exams data started") if (forceRefresh) return@onEach updateData( - DashboardItem.Exams(it.data.orEmpty(), isLoading = true), + DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true), forceRefresh ) - if (!it.data.isNullOrEmpty()) { + if (!it.dataOrNull.isNullOrEmpty()) { firstLoadedItemList += DashboardItem.Type.EXAMS } } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Loading dashboard exams result: Success") - updateData(DashboardItem.Exams(it.data ?: emptyList()), forceRefresh) + updateData(DashboardItem.Exams(it.data), forceRefresh) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Loading dashboard exams result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) updateData(DashboardItem.Exams(error = it.error), forceRefresh) } } - }.launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) + } + .launchWithUniqueRefreshJob("dashboard_exams", forceRefresh) } private fun loadConferences(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { + flatResourceFlow { val semester = semesterRepository.getCurrentSemester(student) conferenceRepository.getConferences( @@ -535,59 +529,62 @@ class DashboardPresenter @Inject constructor( forceRefresh = forceRefresh, startDate = Instant.now(), ) - }.onEach { - when (it.status) { - Status.LOADING -> { - Timber.i("Loading dashboard conferences data started") - if (forceRefresh) return@onEach - updateData( - DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true), - forceRefresh - ) + } + .onEach { + when (it) { + is Resource.Loading -> { + Timber.i("Loading dashboard conferences data started") + if (forceRefresh) return@onEach + updateData( + DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true), + forceRefresh + ) - if (!it.data.isNullOrEmpty()) { - firstLoadedItemList += DashboardItem.Type.CONFERENCES + if (!it.dataOrNull.isNullOrEmpty()) { + firstLoadedItemList += DashboardItem.Type.CONFERENCES + } + } + is Resource.Success -> { + Timber.i("Loading dashboard conferences result: Success") + updateData(DashboardItem.Conferences(it.data), forceRefresh) + } + is Resource.Error -> { + Timber.i("Loading dashboard conferences result: An exception occurred") + errorHandler.dispatch(it.error) + updateData(DashboardItem.Conferences(error = it.error), forceRefresh) } } - Status.SUCCESS -> { - Timber.i("Loading dashboard conferences result: Success") - updateData(DashboardItem.Conferences(it.data ?: emptyList()), forceRefresh) - } - Status.ERROR -> { - Timber.i("Loading dashboard conferences result: An exception occurred") - errorHandler.dispatch(it.error!!) - updateData(DashboardItem.Conferences(error = it.error), forceRefresh) - } } - }.launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) + .launchWithUniqueRefreshJob("dashboard_conferences", forceRefresh) } private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { - flowWithResourceIn { adminMessageRepository.getAdminMessages(student) } - .map { - val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds - it.copy(data = it.data.takeUnless { isDismissed }) + flatResourceFlow { adminMessageRepository.getAdminMessages(student) } + .filter { + val data = it.dataOrNull ?: return@filter true + val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds + !isDismissed } .onEach { - when (it.status) { - Status.LOADING -> { + when (it) { + is Resource.Loading -> { Timber.i("Loading dashboard admin message data started") if (forceRefresh) return@onEach updateData(DashboardItem.AdminMessages(), forceRefresh) } - Status.SUCCESS -> { + is Resource.Success -> { Timber.i("Loading dashboard admin message result: Success") updateData( dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data), forceRefresh = forceRefresh ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Loading dashboard admin message result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) updateData( dashboardItem = DashboardItem.AdminMessages( - adminMessage = it.data, + adminMessage = null, error = it.error ), forceRefresh = forceRefresh @@ -740,7 +737,7 @@ class DashboardPresenter @Inject constructor( if (forceRefresh) { onEach { - if (it.status == Status.SUCCESS) { + if (it is Resource.Success) { cancelJobs(jobName) } }.launch(jobName) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt index 4310ff87..7adb56b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/logviewer/LogViewerPresenter.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.debug.logviewer -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.repositories.LoggerRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -23,19 +23,21 @@ class LogViewerPresenter @Inject constructor( } fun onShareLogsSelected(): Boolean { - flowWithResource { loggerRepository.getLogFiles() }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Loading logs files started") - Status.SUCCESS -> { - Timber.i("Loading logs files result: ${it.data!!.joinToString { file -> file.name }}") - view?.shareLogs(it.data) - } - Status.ERROR -> { - Timber.i("Loading logs files result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { loggerRepository.getLogFiles() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading logs files started") + is Resource.Success -> { + Timber.i("Loading logs files result: ${it.data.joinToString { file -> file.name }}") + view?.shareLogs(it.data) + } + is Resource.Error -> { + Timber.i("Loading logs files result: An exception occurred") + errorHandler.dispatch(it.error) + } } } - }.launch("share") + .launch("share") return true } @@ -44,18 +46,20 @@ class LogViewerPresenter @Inject constructor( } private fun loadLogFile() { - flowWithResource { loggerRepository.getLastLogLines() }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Loading last log file started") - Status.SUCCESS -> { - Timber.i("Loading last log file result: load ${it.data!!.size} lines") - view?.setLines(it.data) - } - Status.ERROR -> { - Timber.i("Loading last log file result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { loggerRepository.getLastLogLines() } + .onEach { + when (it) { + is Resource.Loading -> Timber.d("Loading last log file started") + is Resource.Success -> { + Timber.i("Loading last log file result: load ${it.data.size} lines") + view?.setLines(it.data) + } + is Resource.Error -> { + Timber.i("Loading last log file result: An exception occurred") + errorHandler.dispatch(it.error) + } } } - }.launch("file") + .launch("file") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 582641fc..99b0bcb8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,21 +1,13 @@ package io.github.wulkanowy.ui.modules.exam -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -86,61 +78,57 @@ class ExamPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + } + .launch("holidays") } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading exam data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(createExamItems(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading exam result: Success") - view?.apply { - updateData(createExamItems(it.data!!)) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "exam", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading exam result: An exception occurred") - errorHandler.dispatch(it.error!!) + examRepository.getExams( + student = student, + semester = semester, + start = currentDate.monday, + end = currentDate.sunday, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("load exam data") + .mapResourceData { createExamItems(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "exam", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -181,8 +169,10 @@ class ExamPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 0c18ce95..b6733d4f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester @@ -13,12 +12,10 @@ import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.* import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import javax.inject.Inject @OptIn(FlowPreview::class) @@ -35,7 +32,7 @@ class GradeAverageProvider @Inject constructor( private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = - flowWithResourceIn { + flatResourceFlow { val semesters = semesterRepository.getSemesters(student) when (preferencesRepository.gradeAverageMode) { @@ -81,17 +78,17 @@ class GradeAverageProvider @Inject constructor( val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh) return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> - if (firstSemesterGradeSubject.status == Status.ERROR) { + if (firstSemesterGradeSubject.errorOrNull != null) { return@combine firstSemesterGradeSubject } val isAnyVulcanAverageInFirstSemester = - firstSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } + firstSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } val isAnyVulcanAverageInSecondSemester = - secondSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } + secondSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage } - val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject -> - val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty() + val updatedData = secondSemesterGradeSubject.dataOrNull?.map { secondSemesterSubject -> + val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty() .singleOrNull { it.subject == secondSemesterSubject.subject } val updatedAverage = if (averageMode == ALL_YEAR) { @@ -113,7 +110,7 @@ class GradeAverageProvider @Inject constructor( } secondSemesterSubject.copy(average = updatedAverage) } - secondSemesterGradeSubject.copy(data = updatedData) + secondSemesterGradeSubject.mapData { updatedData!! } } } @@ -166,17 +163,17 @@ class GradeAverageProvider @Inject constructor( val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) - .map { res -> - val (details, summaries) = res.data ?: null to null - val isAnyAverage = summaries.orEmpty().any { it.average != .0 } - val allGrades = details.orEmpty().groupBy { it.subject } + .mapResourceData { res -> + val (details, summaries) = res + val isAnyAverage = summaries.any { it.average != .0 } + val allGrades = details.groupBy { it.subject } - val items = summaries?.emulateEmptySummaries( + val items = summaries.emulateEmptySummaries( student = student, semester = semester, grades = allGrades.toList(), calcAverage = isAnyAverage - )?.map { summary -> + ).map { summary -> val grades = allGrades[summary.subject].orEmpty() GradeSubject( subject = summary.subject, @@ -190,7 +187,7 @@ class GradeAverageProvider @Inject constructor( ) } - Resource(res.status, items, res.error) + items } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 76e88bcd..0ae6521c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -1,16 +1,16 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceData +import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCurrentOrLast -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -99,32 +99,26 @@ class GradePresenter @Inject constructor( } private fun loadData() { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() semesterRepository.getSemesters(student, refreshOnNoCurrent = true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading grade data started") - Status.SUCCESS -> { - val current = it.data!!.getCurrentOrLast() - selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex - schoolYear = current.schoolYear - semesters = it.data.filter { semester -> semester.diaryId == current.diaryId } - view?.setCurrentSemesterName(current.semesterName, schoolYear) - - view?.run { - Timber.i("Loading grade result: Attempt load index $currentPageIndex") - loadChild(currentPageIndex) - showErrorView(false) - showSemesterSwitch(true) - } - } - Status.ERROR -> { - Timber.i("Loading grade result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade data") + .onResourceData { + val current = it.getCurrentOrLast() + selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex + schoolYear = current.schoolYear + semesters = it.filter { semester -> semester.diaryId == current.diaryId } + view?.setCurrentSemesterName(current.semesterName, schoolYear) + view?.run { + Timber.i("Loading grade data: Attempt load index $currentPageIndex") + loadChild(currentPageIndex) + showErrorView(false) + showSemesterSwitch(true) } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { 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 3c747b94..34594111 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 @@ -12,6 +12,7 @@ import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.utils.* + class GradeDetailsDialog : DialogFragment() { private var binding: DialogGradeBinding by lifecycleAwareVariable() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 9141e043..746601a6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.details -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC @@ -14,12 +14,8 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -71,7 +67,7 @@ class GradeDetailsPresenter @Inject constructor( } fun onMarkAsReadSelected(): Boolean { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semesters = semesterRepository.getSemesters(student) val semester = semesters.first { item -> item.semesterId == currentSemesterId } @@ -79,19 +75,11 @@ class GradeDetailsPresenter @Inject constructor( Timber.i("Mark as read ${unreadGrades.size} grades") gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } }) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Select mark grades as read") - Status.SUCCESS -> { - Timber.i("Mark as read result: Success") - loadData(currentSemesterId, false) - } - Status.ERROR -> { - Timber.i("Mark as read result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("mark") + } + .logResourceStatus("mark grades as read") + .onResourceSuccess { loadData(currentSemesterId, false) } + .onResourceError(errorHandler::dispatch) + .launch("mark") return true } @@ -138,71 +126,49 @@ class GradeDetailsPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade details data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) - }.onEach { - Timber.d("Loading grade details status: ${it.status}, data: ${it.data != null}") - when (it.status) { - Status.LOADING -> { - val items = createGradeItems(it.data.orEmpty()) - if (items.isNotEmpty()) { - Timber.i("Loading grade details result: load cached data") - view?.run { - updateNewGradesAmount(it.data.orEmpty()) - enableSwipe(true) - showRefresh(true) - showProgress(false) - showEmpty(false) - showContent(true) - updateData( - data = items, - expandMode = preferencesRepository.gradeExpandMode, - gradeColorTheme = preferencesRepository.gradeColorTheme - ) - notifyParentDataLoaded(semesterId) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade details result: Success") - updateNewGradesAmount(it.data!!) + } + .logResourceStatus("load grade details") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateNewGradesAmount(it) updateMarkAsDoneButton() - val items = createGradeItems(it.data) - view?.run { - showEmpty(items.isEmpty()) - showErrorView(false) - showContent(items.isNotEmpty()) - updateData( - data = items, - expandMode = preferencesRepository.gradeExpandMode, - gradeColorTheme = preferencesRepository.gradeColorTheme - ) - } - analytics.logEvent( - "load_data", - "type" to "grade_details", - "items" to it.data.size + updateData( + data = createGradeItems(it), + expandMode = preferencesRepository.gradeExpandMode, + preferencesRepository.gradeColorTheme ) } - Status.ERROR -> { - Timber.i("Loading grade details result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_details", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded(semesterId) } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded(semesterId) - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun updateNewGradesAmount(grades: List) { @@ -267,15 +233,9 @@ class GradeDetailsPresenter @Inject constructor( } private fun updateGrade(grade: Grade) { - flowWithResource { gradeRepository.updateGrade(grade) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to update grade ${grade.id}") - Status.SUCCESS -> Timber.i("Update grade result: Success") - Status.ERROR -> { - Timber.i("Update grade result: An exception occurred") - errorHandler.dispatch(it.error!!) - } - } - }.launch("update") + resourceFlow { gradeRepository.updateGrade(grade) } + .logResourceStatus("update grade result ${grade.id}") + .onResourceError(errorHandler::dispatch) + .launch("update") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index e536f473..aa0e5999 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -1,19 +1,12 @@ package io.github.wulkanowy.ui.modules.grade.statistics -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.pojos.GradeStatisticsItem -import io.github.wulkanowy.data.repositories.GradeStatisticsRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.repositories.StudentRepository -import io.github.wulkanowy.data.repositories.SubjectRepository +import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -125,33 +118,26 @@ class GradeStatisticsPresenter @Inject constructor( } private fun loadSubjects() { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) subjectRepository.getSubjects(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading grade stats subjects started") - Status.SUCCESS -> { - subjects = requireNotNull(it.data) - Timber.i("Loading grade stats subjects result: Success") - - view?.run { - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - updateSubjects( - data = it.data.map { subject -> subject.name }, - selectedIndex = it.data.indexOfFirst { subject -> - subject.name == currentSubjectName - }, - ) - } - } - Status.ERROR -> { - Timber.i("Loading grade stats subjects result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade stats subjects") + .onResourceData { + subjects = it + view?.run { + showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) + updateSubjects( + data = it.map { subject -> subject.name }, + selectedIndex = it.indexOfFirst { subject -> + subject.name == currentSubjectName + }, + ) } } - }.launch("subjects") + .onResourceError(errorHandler::dispatch) + .launch("subjects") } private fun loadDataByType( @@ -168,7 +154,7 @@ class GradeStatisticsPresenter @Inject constructor( else -> subjectName } - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semesters = semesterRepository.getSemesters(student) val semester = semesters.first { item -> item.semesterId == semesterId } @@ -201,58 +187,43 @@ class GradeStatisticsPresenter @Inject constructor( } } } - }.onEach { - when (it.status) { - Status.LOADING -> { - val isNoContent = it.data == null || checkIsNoContent(it.data, type) - if (!isNoContent) { - view?.run { - showEmpty(isNoContent) - showErrorView(false) - enableSwipe(true) - showRefresh(true) - showProgress(false) - updateData( - newItems = if (isNoContent) emptyList() else it.data!!, - newTheme = preferencesRepository.gradeColorTheme, - showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, - ) - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade stats result: Success") - view?.run { - val isNoContent = checkIsNoContent(it.data!!, type) - showEmpty(isNoContent) - showErrorView(false) - updateData( - newItems = if (isNoContent) emptyList() else it.data, - newTheme = preferencesRepository.gradeColorTheme, - showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, - ) - showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) - } - analytics.logEvent( - "load_data", - "type" to "grade_statistics", - "items" to it.data!!.size + } + .logResourceStatus("load grade stats data") + .mapResourceData { + val isNoContent = checkIsNoContent(it, type) + if (isNoContent) emptyList() else it + } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showEmpty(it.isEmpty()) + updateData( + newItems = it, + newTheme = preferencesRepository.gradeColorTheme, + showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList ) } - Status.ERROR -> { - Timber.i("Loading grade stats result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_statistics", + "items" to it.size + ) + } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) - } - }.launch("load") + .onResourceError(errorHandler::dispatch) + .launch("load") } private fun checkIsNoContent( @@ -267,7 +238,8 @@ class GradeStatisticsPresenter @Inject constructor( items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0 } GradeStatisticsItem.DataType.POINTS -> { - items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } ?: false + items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } + ?: false } } } 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 933633dc..b07570cb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -8,9 +8,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -37,56 +34,40 @@ class GradeSummaryPresenter @Inject constructor( } private fun loadData(semesterId: Int, forceRefresh: Boolean) { - Timber.i("Loading grade summary started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) - }.onEach { - Timber.d("Loading grade summary status: ${it.status}, data: ${it.data != null}") - when (it.status) { - Status.LOADING -> { - val items = createGradeSummaryItems(it.data.orEmpty()) - if (items.isNotEmpty()) { - Timber.i("Loading grade summary result: load cached data") - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showEmpty(false) - showContent(true) - updateData(items) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading grade summary result: Success") - val items = createGradeSummaryItems(it.data!!) - view?.run { - showEmpty(items.isEmpty()) - showContent(items.isNotEmpty()) - showErrorView(false) - updateData(items) - } - analytics.logEvent( - "load_data", - "type" to "grade_summary", - "items" to it.data.size - ) - } - Status.ERROR -> { - Timber.i("Loading grade summary result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load grade summary", showData = true) + .mapResourceData { createGradeSummaryItems(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded(semesterId) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "grade_summary", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showRefresh(false) + showProgress(false) + notifyParentDataLoaded(semesterId) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -153,9 +134,9 @@ class GradeSummaryPresenter @Inject constructor( private fun checkEmpty(gradeSummary: GradeSubject): Boolean { return gradeSummary.run { summary.finalGrade.isBlank() - && summary.predictedGrade.isBlank() - && average == .0 - && points.isBlank() + && summary.predictedGrade.isBlank() + && average == .0 + && points.isBlank() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index d7d5d7cb..2ac552b4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,21 +1,13 @@ package io.github.wulkanowy.ui.modules.homework -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -89,61 +81,59 @@ class HomeworkPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) - currentDate = baseDate - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + } + .launch("holidays") } private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading homework data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(createHomeworkItem(it.data)) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading homework result: Success") - view?.apply { - updateData(createHomeworkItem(it.data!!)) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "homework", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading homework result: An exception occurred") - errorHandler.dispatch(it.error!!) + homeworkRepository.getHomework( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("loading homework") + .mapResourceData { createHomeworkItem(it) } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "homework", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -159,9 +149,10 @@ class HomeworkPresenter @Inject constructor( private fun createHomeworkItem(items: List): List> { return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> - listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam -> - HomeworkItem(exam, HomeworkItem.ViewType.ITEM) - } + listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed() + .map { exam -> + HomeworkItem(exam, HomeworkItem.ViewType.ITEM) + } }.flatten() } @@ -184,8 +175,10 @@ class HomeworkPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt index 3639c2fe..a21f6aef 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddPresenter.kt @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.homework.add -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toLocalDate -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -55,7 +56,7 @@ class HomeworkAddPresenter @Inject constructor( } private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) val entryDate = LocalDate.now() @@ -72,21 +73,15 @@ class HomeworkAddPresenter @Inject constructor( attachments = emptyList(), ).apply { isAddedByUser = true } ) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework insert start") - Status.SUCCESS -> { - Timber.i("Homework insert: Success") - view?.run { - showSuccessMessage() - closeDialog() - } - } - Status.ERROR -> { - Timber.i("Homework insert result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("homework insert") + .onResourceSuccess { + view?.run { + showSuccessMessage() + closeDialog() } } - }.launch("add_homework") + .onResourceError(errorHandler::dispatch) + .launch("add_homework") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index ea9b47a0..e76df6bd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.homework.details -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -34,38 +35,26 @@ class HomeworkDetailsPresenter @Inject constructor( } fun deleteHomework(homework: Homework) { - flowWithResource { homeworkRepository.deleteHomework(homework) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework delete start") - Status.SUCCESS -> { - Timber.i("Homework delete: Success") - view?.run { - showMessage(homeworkDeleteSuccess) - closeDialog() - } - } - Status.ERROR -> { - Timber.i("Homework delete result: An exception occurred") - errorHandler.dispatch(it.error!!) + resourceFlow { homeworkRepository.deleteHomework(homework) } + .logResourceStatus("homework delete") + .onResourceSuccess { + view?.run { + showMessage(homeworkDeleteSuccess) + closeDialog() } } - }.launch("delete") + .onResourceError(errorHandler::dispatch) + .launch("delete") } fun toggleDone(homework: Homework) { - flowWithResource { homeworkRepository.toggleDone(homework) }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Homework details update start") - Status.SUCCESS -> { - Timber.i("Homework details update: Success") - view?.updateMarkAsDoneLabel(homework.isDone) - analytics.logEvent("homework_mark_as_done") - } - Status.ERROR -> { - Timber.i("Homework details update result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + resourceFlow { homeworkRepository.toggleDone(homework) } + .logResourceStatus("homework details update") + .onResourceSuccess { + view?.updateMarkAsDoneLabel(homework.isDone) + analytics.logEvent("homework_mark_as_done") } - }.launch("toggle") + .onResourceError(errorHandler::dispatch) + .launch("toggle") } } 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 3543a304..1b42c6c5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -1,15 +1,16 @@ package io.github.wulkanowy.ui.modules.login.advanced -import io.github.wulkanowy.data.Status +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.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.sdk.Sdk 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.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -129,20 +130,20 @@ class LoginAdvancedPresenter @Inject constructor( fun onSignInClick() { if (!validateCredentials()) return - flowWithResource { getStudentsAppropriatesToLoginType() }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Login started") - hideSoftKeyboard() - showProgress(true) - showContent(false) - } - Status.SUCCESS -> { - Timber.i("Login result: Success") - analytics.logEvent( - "registration_form", + resourceFlow { getStudentsAppropriatesToLoginType() } + .logResourceStatus("login") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + hideSoftKeyboard() + showProgress(true) + showContent(false) + } + is Resource.Success -> { + analytics.logEvent( + "registration_form", "success" to true, - "students" to it.data!!.size, + "students" to it.data.size, "error" to "No error" ) val loginData = LoginData( @@ -154,23 +155,22 @@ class LoginAdvancedPresenter @Inject constructor( 0 -> view?.navigateToSymbol(loginData) else -> view?.navigateToStudentSelect(it.data) } + } + is Resource.Error -> { + analytics.logEvent( + "registration_form", + "success" to false, "students" to -1, + "error" to it.error.message.ifNullOrBlank { "No message" } + ) + loginErrorHandler.dispatch(it.error) + } } - Status.ERROR -> { - Timber.i("Login result: An exception occurred") - analytics.logEvent( - "registration_form", - "success" to false, "students" to -1, - "error" to it.error!!.message.ifNullOrBlank { "No message" } - ) - loginErrorHandler.dispatch(it.error) + }.onResourceNotLoading { + view?.apply { + showProgress(false) + showContent(true) } - } - }.afterLoading { - view?.apply { - showProgress(false) - showContent(true) - } - }.launch("login") + }.launch("login") } private suspend fun getStudentsAppropriatesToLoginType(): List { 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 49be6fbb..b4291ff4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.login.form import androidx.core.net.toUri -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.StudentRepository 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.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.net.URL import javax.inject.Inject @@ -75,7 +72,7 @@ class LoginFormPresenter @Inject constructor( val username = view?.formUsernameValue.orEmpty().trim() if ("@" in username && "@vulcan" !in username) { - val hosts = view?.getHostsValues().orEmpty().map { it.toUri().host to it }.toMap() + val hosts = view?.getHostsValues().orEmpty().associateBy { it.toUri().host } val usernameHost = username.substringAfter("@") hosts[usernameHost]?.let { @@ -95,54 +92,54 @@ class LoginFormPresenter @Inject constructor( if (!validateCredentials(email, password, host)) return - flowWithResource { + resourceFlow { studentRepository.getStudentsScrapper( email = email, password = password, scrapperBaseUrl = host, symbol = symbol ) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Login started") + } + .logResourceStatus("login") + .onResourceLoading { + view?.run { hideSoftKeyboard() showProgress(true) showContent(false) } - Status.SUCCESS -> { - Timber.i("Login result: Success") - analytics.logEvent( - "registration_form", - "success" to true, - "students" to it.data!!.size, - "scrapperBaseUrl" to host, - "error" to "No error" - ) - when (it.data.size) { - 0 -> view?.navigateToSymbol(LoginData(email, password, host)) - else -> view?.navigateToStudentSelect(it.data) - } + } + .onResourceSuccess { + when (it.size) { + 0 -> view?.navigateToSymbol(LoginData(email, password, host)) + else -> view?.navigateToStudentSelect(it) } - Status.ERROR -> { - Timber.i("Login result: An exception occurred") - analytics.logEvent( - "registration_form", - "success" to false, - "students" to -1, - "scrapperBaseUrl" to host, - "error" to it.error!!.message.ifNullOrBlank { "No message" }) - loginErrorHandler.dispatch(it.error) - lastError = it.error - view?.showContact(true) + analytics.logEvent( + "registration_form", + "success" to true, + "students" to it.size, + "scrapperBaseUrl" to host, + "error" to "No error" + ) + } + .onResourceNotLoading { + view?.apply { + showProgress(false) + showContent(true) } } - }.afterLoading { - view?.apply { - showProgress(false) - showContent(true) + .onResourceError { + loginErrorHandler.dispatch(it) + lastError = it + view?.showContact(true) + analytics.logEvent( + "registration_form", + "success" to false, + "students" to -1, + "scrapperBaseUrl" to host, + "error" to it.message.ifNullOrBlank { "No message" } + ) } - }.launch("login") + .launch("login") } fun onFaqClick() { 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 271e8a8a..3d049301 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 @@ -1,12 +1,12 @@ package io.github.wulkanowy.ui.modules.login.recover -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.repositories.RecoverRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -57,24 +57,28 @@ class LoginRecoverPresenter @Inject constructor( if (!validateInput(username, host)) return - flowWithResource { recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + resourceFlow { + recoverRepository.getReCaptchaSiteKey( + host, + symbol.ifBlank { "Default" }) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { hideSoftKeyboard() showRecoverForm(false) showProgress(true) showErrorView(false) showCaptcha(false) } - Status.SUCCESS -> view?.run { - loadReCaptcha(url = it.data!!.first, siteKey = it.data.second) + is Resource.Success -> view?.run { + loadReCaptcha(url = it.data.first, siteKey = it.data.second) showProgress(false) showErrorView(false) showCaptcha(true) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Obtain captcha site key result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } }.launch("captcha") @@ -101,26 +105,43 @@ class LoginRecoverPresenter @Inject constructor( val host = view?.recoverHostValue.orEmpty() val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } - flowWithResource { recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + resourceFlow { + recoverRepository.sendRecoverRequest( + host, + symbol, + username, + reCaptchaResponse + ) + }.onEach { + when (it) { + is Resource.Loading -> view?.run { showProgress(true) showRecoverForm(false) showCaptcha(false) } - Status.SUCCESS -> view?.run { + is Resource.Success -> view?.run { showSuccessView(true) - setSuccessTitle(it.data!!.substringBefore(". ")) + setSuccessTitle(it.data.substringBefore(". ")) setSuccessMessage(it.data.substringAfter(". ")) - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to true) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to true + ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Send recover request result: An exception occurred") - errorHandler.dispatch(it.error!!) - analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to false) + errorHandler.dispatch(it.error) + analytics.logEvent( + "account_recover", + "register" to host, + "symbol" to symbol, + "success" to false + ) } } - }.afterLoading { + }.onResourceNotLoading { view?.showProgress(false) }.launch("verified") } 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 71c60e62..3455b3cf 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,14 +1,15 @@ package io.github.wulkanowy.ui.modules.login.studentselect -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -66,16 +67,16 @@ class LoginStudentSelectPresenter @Inject constructor( private fun loadData(studentsWithSemesters: List) { resetSelectedState() - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Login student select students load started") - Status.SUCCESS -> view?.updateData(studentsWithSemesters.map { studentWithSemesters -> - studentWithSemesters to it.data!!.any { item -> + 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) } }) - Status.ERROR -> { - errorHandler.dispatch(it.error!!) + is Resource.Error -> { + errorHandler.dispatch(it.error) lastError = it.error view?.updateData(studentsWithSemesters.map { student -> student to false }) } @@ -89,29 +90,27 @@ class LoginStudentSelectPresenter @Inject constructor( } private fun registerStudents(studentsWithSemesters: List) { - flowWithResource { studentRepository.saveStudents(studentsWithSemesters) } + resourceFlow { studentRepository.saveStudents(studentsWithSemesters) } + .logResourceStatus("registration") .onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Registration started") + when (it) { + is Resource.Loading -> view?.run { showProgress(true) showContent(false) } - Status.SUCCESS -> { - Timber.i("Registration result: Success") + is Resource.Success -> { syncManager.startOneTimeSyncWorker(quiet = true) view?.openMainView() logRegisterEvent(studentsWithSemesters) } - Status.ERROR -> { - Timber.i("Registration result: An exception occurred ") + is Resource.Error -> { view?.apply { showProgress(false) showContent(true) showContact(true) } lastError = it.error - loginErrorHandler.dispatch(it.error!!) + loginErrorHandler.dispatch(it.error) logRegisterEvent(studentsWithSemesters, it.error) } } 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 7e195893..691cd448 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,13 +1,13 @@ package io.github.wulkanowy.ui.modules.login.symbol -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -45,7 +45,7 @@ class LoginSymbolPresenter @Inject constructor( return } - flowWithResource { + resourceFlow { studentRepository.getStudentsScrapper( email = loginData.login, password = loginData.password, @@ -53,15 +53,15 @@ class LoginSymbolPresenter @Inject constructor( symbol = symbol, ) }.onEach { - when (it.status) { - Status.LOADING -> view?.run { + when (it) { + is Resource.Loading -> view?.run { Timber.i("Login with symbol started") hideSoftKeyboard() showProgress(true) showContent(false) } - Status.SUCCESS -> { - when (it.data?.size) { + is Resource.Success -> { + when (it.data.size) { 0 -> { Timber.i("Login with symbol result: Empty student list") view?.run { @@ -77,13 +77,13 @@ class LoginSymbolPresenter @Inject constructor( analytics.logEvent( "registration_symbol", "success" to true, - "students" to it.data!!.size, + "students" to it.data.size, "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, "error" to "No error" ) } - Status.ERROR -> { + is Resource.Error -> { Timber.i("Login with symbol result: An exception occurred") analytics.logEvent( "registration_symbol", @@ -91,14 +91,14 @@ class LoginSymbolPresenter @Inject constructor( "students" to -1, "scrapperBaseUrl" to loginData.baseUrl, "symbol" to symbol, - "error" to it.error!!.message.ifNullOrBlank { "No message" } + "error" to it.error.message.ifNullOrBlank { "No message" } ) loginErrorHandler.dispatch(it.error) lastError = it.error view?.showContact(true) } } - }.afterLoading { + }.onResourceNotLoading { view?.apply { showProgress(false) showContent(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index fd0598d8..6f5c8e74 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -1,14 +1,11 @@ package io.github.wulkanowy.ui.modules.luckynumber -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -34,47 +31,45 @@ class LuckyNumberPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() luckyNumberRepository.getLuckyNumber(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading lucky number started") - Status.SUCCESS -> { - if (it.data != null) { - Timber.i("Loading lucky number result: Success") - view?.apply { - updateData(it.data) - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent( - "load_item", - "type" to "lucky_number", - "number" to it.data.luckyNumber - ) - } else { - Timber.i("Loading lucky number result: No lucky number found") - view?.run { - showContent(false) - showEmpty(true) - showErrorView(false) - } + } + .logResourceStatus("load lucky number") + .onResourceData { + if (it != null) { + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else { + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) } } - Status.ERROR -> { - Timber.i("Loading lucky number result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "lucky_number", + "number" to it.luckyNumber + ) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt index c45cb69a..fc753950 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt @@ -1,24 +1,12 @@ package io.github.wulkanowy.ui.modules.luckynumber.history -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.previousOrSameSchoolDay -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onEach +import io.github.wulkanowy.utils.* +import kotlinx.coroutines.flow.* import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -52,55 +40,51 @@ class LuckyNumberHistoryPresenter @Inject constructor( flow { val student = studentRepository.getCurrentStudent() emit(semesterRepository.getCurrentSemester(student)) - }.catch { - Timber.i("Loading semester result: An exception occurred") - }.onEach { - currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) - reloadNavigation() - }.launch("holidays") + } + .catch { Timber.i("Loading semester result: An exception occurred") } + .onEach { + currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) + reloadNavigation() + } + .launch("holidays") } private fun loadData() { - flowWithResource { + flow { val student = studentRepository.getCurrentStudent() - luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading lucky number history started") - Status.SUCCESS -> { - if (!it.data?.first().isNullOrEmpty()) { - Timber.i("Loading lucky number result: Success") - view?.apply { - updateData(it.data!!.first()) - showContent(true) - showEmpty(false) - showErrorView(false) - showProgress(false) - } - analytics.logEvent( - "load_items", - "type" to "lucky_number_history", - "numbers" to it.data - ) - } else { - Timber.i("Loading lucky number history result: No lucky numbers found") - view?.run { - showContent(false) - showEmpty(true) - showErrorView(false) - } + emitAll( + luckyNumberRepository.getLuckyNumberHistory( + student = student, + start = currentDate.monday, + end = currentDate.sunday + ) + ) + } + .onEach { + if (!it.isNullOrEmpty()) { + view?.apply { + updateData(it) + showContent(true) + showEmpty(false) + showErrorView(false) + showProgress(false) + } + } else { + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) + showProgress(false) } } - Status.ERROR -> { - Timber.i("Loading lucky number history result: An exception occurred") - errorHandler.dispatch(it.error!!) - } + + analytics.logEvent( + "load_items", + "type" to "lucky_number_history", + ) } - }.afterLoading { - view?.run { - showProgress(false) - } - }.launch() + .catch { errorHandler.dispatch(it) } + .launchIn(presenterScope) } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -143,8 +127,10 @@ class LuckyNumberHistoryPresenter @Inject constructor( view?.apply { showPreButton(!currentDate.minusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + - currentDate.sunday.toFormattedString("dd.MM")) + updateNavigationWeek( + "${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM") + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 5b6af69a..cac648da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -1,14 +1,14 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -47,16 +47,15 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Lucky number widget configure students data load") - Status.SUCCESS -> { + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Lucky number widget configure students data load") + is Resource.Success -> { val selectedStudentId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } ?: -1 - when { - it.data!!.isEmpty() -> view?.openLoginView() + it.data.isEmpty() -> view?.openLoginView() it.data.size == 1 -> { selectedStudent = it.data.single().student view?.showThemeDialog() @@ -64,7 +63,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( else -> view?.updateData(it.data, selectedStudentId) } } - Status.ERROR -> errorHandler.dispatch(it.error!!) + is Resource.Error -> errorHandler.dispatch(it.error) } }.launch() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index 2b2d18fa..e016c07e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -13,14 +13,17 @@ import android.view.View.VISIBLE import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.Resource +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.utils.PendingIntentCompat -import io.github.wulkanowy.utils.toFirstResult import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @@ -66,12 +69,16 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) + if (luckyNumber is Resource.Error) { + Timber.e("Error loading lucky number for widget", luckyNumber.error) + } + val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) .apply { setTextViewText( R.id.luckyNumberWidgetNumber, - luckyNumber?.luckyNumber?.toString() ?: "#" + luckyNumber.dataOrNull?.toString() ?: "#" ) setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) } @@ -167,14 +174,17 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { else -> null } - currentStudent?.let { - luckyNumberRepository.getLuckyNumber(it, false).toFirstResult().data + if (currentStudent != null) { + luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false) + .toFirstResult() + } else { + Resource.Success(null) } } catch (e: Exception) { if (e.cause !is NoCurrentStudentException) { Timber.e(e, "An error has occurred in lucky number provider") } - null + Resource.Error(e) } } 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 821b1e6f..e01497b9 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 @@ -1,9 +1,12 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceError +import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BaseView @@ -16,8 +19,6 @@ import io.github.wulkanowy.ui.modules.message.MessageView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.time.Duration import java.time.Instant @@ -75,20 +76,14 @@ class MainPresenter @Inject constructor( return } - flowWithResource { studentRepository.getSavedStudents(false) } - .onEach { resource -> - when (resource.status) { - Status.LOADING -> Timber.i("Loading student avatar data started") - Status.SUCCESS -> { - studentsWitSemesters = resource.data - showCurrentStudentAvatar() - } - Status.ERROR -> { - Timber.i("Loading student avatar result: An exception occurred") - errorHandler.dispatch(resource.error!!) - } - } - }.launch("avatar") + resourceFlow { studentRepository.getSavedStudents(false) } + .logResourceStatus("load student avatar") + .onResourceSuccess { + studentsWitSemesters = it + showCurrentStudentAvatar() + } + .onResourceError(errorHandler::dispatch) + .launch("avatar") } fun onViewChange(destinationView: BaseView) { 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 eb33ee6e..39c337bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.message.preview import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.enums.MessageFolder @@ -10,11 +10,8 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -53,44 +50,43 @@ class MessagePreviewPresenter @Inject constructor( view?.showErrorDetailsDialog(lastError) } - private fun loadData(message: Message) { - flowWithResourceIn { - val student = studentRepository.getStudentById(message.studentId) - messageRepository.getMessage(student, message, true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started") - Status.SUCCESS -> { - Timber.i("Loading message ${message.messageId} preview result: Success ") - if (it.data != null) { - this@MessagePreviewPresenter.message = it.data.message - this@MessagePreviewPresenter.attachments = it.data.attachments - view?.apply { - setMessageWithAttachment(it.data) - showContent(true) - initOptions() - } - analytics.logEvent( - "load_item", - "type" to "message_preview", - "length" to it.data.message.content.length - ) - } else { - view?.run { - showMessage(messageNotExists) - popView() - } + private fun loadData(messageToLoad: Message) { + flatResourceFlow { + val student = studentRepository.getStudentById(messageToLoad.studentId) + messageRepository.getMessage(student, messageToLoad, true) + } + .logResourceStatus("message ${messageToLoad.messageId} preview") + .onResourceData { + if (it != null) { + message = it.message + attachments = it.attachments + view?.apply { + setMessageWithAttachment(it) + showContent(true) + initOptions() + } + } else { + view?.run { + showMessage(messageNotExists) + popView() } } - Status.ERROR -> { - Timber.i("Loading message ${message.messageId} preview result: An exception occurred ") - retryCallback = { onMessageLoadRetry(message) } - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent( + "load_item", + "type" to "message_preview", + "length" to it.message.content.length + ) } } - }.afterLoading { - view?.showProgress(false) - }.launch() + .onResourceNotLoading { view?.showProgress(false) } + .onResourceError { + retryCallback = { onMessageLoadRetry(messageToLoad) } + errorHandler.dispatch(it) + } + .launch() } fun onReply(): Boolean { @@ -176,28 +172,26 @@ class MessagePreviewPresenter @Inject constructor( showErrorView(false) } - flowWithResource { - val student = studentRepository.getCurrentStudent() - messageRepository.deleteMessage(student, message!!) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Message ${message?.id} delete started") - Status.SUCCESS -> { - Timber.d("Message ${message?.id} delete success") + Timber.i("Delete message ${message?.id}") + + presenterScope.launch { + runCatching { + val student = studentRepository.getCurrentStudent() + messageRepository.deleteMessage(student, message!!) + } + .onFailure { + retryCallback = { onMessageDelete() } + errorHandler.dispatch(it) + } + .onSuccess { view?.run { showMessage(deleteMessageSuccessString) popView() } } - Status.ERROR -> { - Timber.d("Message ${message?.id} delete failed") - retryCallback = { onMessageDelete() } - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { + view?.showProgress(false) - }.launch("delete") + } } private fun showErrorViewOnError(message: String, error: Throwable) { 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 5e961efc..e5770955 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -1,25 +1,20 @@ package io.github.wulkanowy.ui.modules.message.send -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.logResourceStatus +import io.github.wulkanowy.data.onResourceNotLoading import io.github.wulkanowy.data.pojos.MessageDraft -import io.github.wulkanowy.data.repositories.MessageRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository -import io.github.wulkanowy.data.repositories.RecipientRepository -import io.github.wulkanowy.data.repositories.ReportingUnitRepository -import io.github.wulkanowy.data.repositories.SemesterRepository -import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.* +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.onEach @@ -55,10 +50,12 @@ class SendMessagePresenter @Inject constructor( setContent(it) } message?.let { - setSubject(when (reply) { - true -> "Re: " - else -> "FW: " - } + message.subject) + setSubject( + when (reply) { + true -> "Re: " + else -> "FW: " + } + message.subject + ) if (preferencesRepository.fillMessageContent || reply != true) { setContent( when (reply) { @@ -67,7 +64,8 @@ class SendMessagePresenter @Inject constructor( } + when (message.sender.isNotEmpty()) { true -> "Od: ${message.sender}\n" false -> "Do: ${message.recipient}\n" - } + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}") + } + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}" + ) } } } @@ -111,7 +109,7 @@ class SendMessagePresenter @Inject constructor( } private fun loadData(message: Message?, reply: Boolean?) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) val unit = reportingUnitRepository.getReportingUnit(student, semester.unitId) @@ -125,58 +123,64 @@ class SendMessagePresenter @Inject constructor( Timber.i("Loading message recipients started") val messageRecipients = when { - message != null && reply == true -> recipientRepository.getMessageRecipients(student, message) + message != null && reply == true -> recipientRepository.getMessageRecipients( + student, + message + ) else -> emptyList() }.let { createChips(it) } - Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", messageRecipients.size) + Timber.i( + "Loaded message recipients to reply result: Success, fetched %d recipients", + messageRecipients.size + ) Triple(unit, recipients, messageRecipients) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Loading recipients started") - showProgress(true) - showContent(false) - } - Status.SUCCESS -> it.data!!.let { (reportingUnit, recipientChips, selectedRecipientChips) -> - view?.run { - if (reportingUnit != null) { - setReportingUnit(reportingUnit) - setRecipients(recipientChips) - if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips) - showContent(true) - } else { - Timber.i("Loading recipients result: Can't find the reporting unit") - view?.showEmpty(true) + } + .logResourceStatus("load recipients") + .onEach { + when (it) { + is Resource.Loading -> view?.run { + showProgress(true) + showContent(false) + } + is Resource.Success -> it.data.let { (reportingUnit, recipientChips, selectedRecipientChips) -> + view?.run { + if (reportingUnit != null) { + setReportingUnit(reportingUnit) + setRecipients(recipientChips) + if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( + selectedRecipientChips + ) + showContent(true) + } else { + Timber.i("Loading recipients result: Can't find the reporting unit") + view?.showEmpty(true) + } } } + is Resource.Error -> { + view?.showContent(true) + errorHandler.dispatch(it.error) + } } - Status.ERROR -> { - Timber.i("Loading recipients result: An exception occurred") - view?.showContent(true) - errorHandler.dispatch(it.error!!) - } - } - }.afterLoading { - view?.run { showProgress(false) } - }.launch() + }.onResourceNotLoading { + view?.run { showProgress(false) } + }.launch() } private fun sendMessage(subject: String, content: String, recipients: List) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() messageRepository.sendMessage(student, subject, content, recipients) - }.onEach { - when (it.status) { - Status.LOADING -> view?.run { - Timber.i("Sending message started") + }.logResourceStatus("sending message").onEach { + when (it) { + is Resource.Loading -> view?.run { showSoftInput(false) showContent(false) showProgress(true) showActionBar(false) } - Status.SUCCESS -> { - Timber.i("Sending message result: Success") + is Resource.Success -> { view?.clearDraft() view?.run { showMessage(messageSuccess) @@ -184,14 +188,13 @@ class SendMessagePresenter @Inject constructor( } analytics.logEvent("send_message", "recipients" to recipients.size) } - Status.ERROR -> { - Timber.i("Sending message result: An exception occurred") + is Resource.Error -> { view?.run { showContent(true) showProgress(false) showActionBar(true) } - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } }.launch("send") @@ -259,7 +262,8 @@ class SendMessagePresenter @Inject constructor( } fun getRecipientsNames(): String { - return messageRepository.draftMessage?.recipients.orEmpty().joinToString { it.recipient.name } + return messageRepository.draftMessage?.recipients.orEmpty() + .joinToString { it.recipient.name } } fun clearDraft() { @@ -267,6 +271,7 @@ class SendMessagePresenter @Inject constructor( Timber.i("Draft cleared!") } - fun getMessageBackupContent(recipients: String) = if (recipients.isEmpty()) view?.getMessageBackupDialogString() + fun getMessageBackupContent(recipients: String) = + if (recipients.isEmpty()) view?.getMessageBackupDialogString() else view?.getMessageBackupDialogStringWithRecipients(recipients) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index f70a1bab..57055a64 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.repositories.MessageRepository @@ -9,17 +9,10 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.xdrop.fuzzywuzzy.FuzzySearch import timber.log.Timber @@ -107,64 +100,75 @@ class MessageTabPresenter @Inject constructor( ) { Timber.i("Loading $folder message data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) messageRepository.getMessages(student, semester, folder, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showErrorView(false) - showRefresh(true) - showProgress(false) - showContent(true) - messages = it.data - val filteredData = getFilteredData( + } + .logResourceStatus("load $folder message") + .onEach { + when (it) { + is Resource.Intermediate -> { + if (it.data.isNotEmpty()) { + view?.run { + enableSwipe(true) + showErrorView(false) + showRefresh(true) + showProgress(false) + showContent(true) + messages = it.data + val filteredData = getFilteredData( + lastSearchQuery, + onlyUnread, + onlyWithAttachments + ) + val messageItems = filteredData.map { message -> + MessageTabDataItem.MessageItem(message) + } + val messageItemsWithHeader = + listOf(MessageTabDataItem.Header) + messageItems + + updateData( + messageItemsWithHeader, + folder.id == MessageFolder.SENT.id + ) + notifyParentDataLoaded() + } + } + } + is Resource.Success -> { + messages = it.data + updateData( + getFilteredData( lastSearchQuery, onlyUnread, onlyWithAttachments ) - val messageItems = filteredData.map { message -> - MessageTabDataItem.MessageItem(message) - } - val messageItemsWithHeader = - listOf(MessageTabDataItem.Header) + messageItems - - updateData(messageItemsWithHeader, folder.id == MessageFolder.SENT.id) - notifyParentDataLoaded() - } + ) + analytics.logEvent( + "load_data", + "type" to "messages", + "items" to it.data.size, + "folder" to folder.name + ) } - } - Status.SUCCESS -> { - Timber.i("Loading $folder message result: Success") - messages = it.data!! - updateData(getFilteredData(lastSearchQuery, onlyUnread, onlyWithAttachments)) - analytics.logEvent( - "load_data", - "type" to "messages", - "items" to it.data.size, - "folder" to folder.name - ) - } - Status.ERROR -> { - Timber.i("Loading $folder message result: An exception occurred") - errorHandler.dispatch(it.error!!) + else -> {} } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceNotLoading { + view?.run { + showRefresh(false) + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index 53049891..36a720e5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.mobiledevice -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.repositories.MobileDeviceRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,10 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -52,49 +48,39 @@ class MobileDevicePresenter @Inject constructor( private fun loadData(forceRefresh: Boolean = false) { Timber.i("Loading mobile devices data started") - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.getDevices(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading mobile devices result: Success") - view?.run { - updateData(it.data!!) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "devices", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading mobile devices result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load mobile devices data") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "devices", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -128,25 +114,19 @@ class MobileDevicePresenter @Inject constructor( } fun onUnregisterConfirmed(device: MobileDevice) { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.unregisterDevice(student, semester, device) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Unregister device started") - Status.SUCCESS -> { - Timber.i("Unregister device result: Success") - view?.run { - showProgress(false) - enableSwipe(true) - } - } - Status.ERROR -> { - Timber.i("Unregister device result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("unregister device") + .onResourceSuccess { + view?.run { + showProgress(false) + enableSwipe(true) } } - }.launch("unregister") + .onResourceError(errorHandler::dispatch) + .launch("unregister") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index 5e7110ee..875b73ad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.MobileDeviceRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -29,29 +26,29 @@ class MobileDeviceTokenPresenter @Inject constructor( } private fun loadData() { - flowWithResource { + resourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) mobileDeviceRepository.getToken(student, semester) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Mobile device registration data started") - Status.SUCCESS -> { - Timber.i("Mobile device registration result: Success") - view?.run { - updateData(it.data!!) - showContent() - } - analytics.logEvent("device_register", "symbol" to it.data!!.token.substring(0, 3)) - } - Status.ERROR -> { - Timber.i("Mobile device registration result: An exception occurred") - view?.closeDialog() - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load mobile device registration") + .onResourceData { + view?.run { + updateData(it) + showContent() } } - }.afterLoading { - view?.hideLoading() - }.launch() + .onResourceSuccess { + analytics.logEvent( + "device_register", + "symbol" to it.token.substring(0, 3) + ) + } + .onResourceNotLoading { view?.hideLoading() } + .onResourceError { + view?.closeDialog() + errorHandler.dispatch(it) + } + .launch() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 10a39182..440565e1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.note -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.repositories.NoteRepository import io.github.wulkanowy.data.repositories.SemesterRepository @@ -8,9 +8,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResource -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -51,51 +48,40 @@ class NotePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading note data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) noteRepository.getNotes(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedByDescending { item -> item.date }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading note result: Success") - view?.apply { - updateData(it.data!!.sortedByDescending { item -> item.date }) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "note", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading note result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load note data") + .mapResourceData { it.sortedByDescending { note -> note.date } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "note", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { @@ -122,14 +108,14 @@ class NotePresenter @Inject constructor( } private fun updateNote(note: Note) { - flowWithResource { noteRepository.updateNote(note) } + resourceFlow { noteRepository.updateNote(note) } .onEach { - when (it.status) { - Status.LOADING -> Timber.i("Attempt to update note ${note.id}") - Status.SUCCESS -> Timber.i("Update note result: Success") - Status.ERROR -> { + when (it) { + is Resource.Loading -> Timber.i("Attempt to update note ${note.id}") + is Resource.Success -> Timber.i("Update note result: Success") + is Resource.Error -> { Timber.i("Update note result: An exception occurred") - errorHandler.dispatch(it.error!!) + errorHandler.dispatch(it.error) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index ac8c273e..262398b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.SchoolRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -65,46 +62,48 @@ class SchoolPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) schoolRepository.getSchoolInfo(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading school info started") - Status.SUCCESS -> if (it.data != null) { - Timber.i("Loading teachers result: Success") + } + .logResourceStatus("load school info") + .onResourceData { + if (it != null) { view?.run { - address = it.data.address.ifBlank { null } - contact = it.data.contact.ifBlank { null } - updateData(it.data) + address = it.address.ifBlank { null } + contact = it.contact.ifBlank { null } + updateData(it) showContent(true) showEmpty(false) showErrorView(false) } - analytics.logEvent("load_item", "type" to "school") } else view?.run { Timber.i("Loading school result: No school info found") showContent(!isViewEmpty) showEmpty(isViewEmpty) showErrorView(false) } - Status.ERROR -> { - Timber.i("Loading school result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "school") } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index bd46ff0b..e2af05c9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -1,16 +1,13 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -53,43 +50,41 @@ class TeacherPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) teacherRepository.getTeachers(student, semester, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading teachers data started") - Status.SUCCESS -> { - Timber.i("Loading teachers result: Success") - view?.run { - updateData(it.data!!.filter { item -> item.name.isNotBlank() }) - showContent(it.data.isNotEmpty()) - showEmpty(it.data.isEmpty()) - showErrorView(false) - } - analytics.logEvent( - "load_data", - "type" to "teachers", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading teachers result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load teachers data") + .onResourceData { + view?.run { + updateData(it.filter { item -> item.name.isNotBlank() }) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + showErrorView(false) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) - notifyParentDataLoaded() + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "teachers", + "items" to it.size + ) } - }.catch { - errorHandler.dispatch(it) - view?.notifyParentDataLoaded() - }.launch() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + notifyParentDataLoaded() + } + } + .onResourceError(errorHandler::dispatch) + .catch { + errorHandler.dispatch(it) + view?.notifyParentDataLoaded() + } + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt index 62c93198..f77a8833 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementPresenter.kt @@ -1,15 +1,12 @@ package io.github.wulkanowy.ui.modules.schoolannouncement -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -52,50 +49,37 @@ class SchoolAnnouncementPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading School announcement data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showErrorView(false) - showProgress(false) - showContent(true) - updateData(it.data) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading School announcement result: Success") - view?.apply { - updateData(it.data!!) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_school_announcement", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading School announcement result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load school announcement") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceSuccess { + analytics.logEvent( + "load_school_announcement", + "items" to it.size + ) } - }.launch() + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch("load_data") } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt index 80798b11..083b590b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.studentinfo -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.StudentInfo import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentInfoRepository @@ -8,10 +8,7 @@ import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getCurrentOrLast -import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -72,51 +69,50 @@ class StudentInfoPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - flowWithResourceIn { + flatResourceFlow { val semester = studentWithSemesters.semesters.getCurrentOrLast() studentInfoRepository.getStudentInfo( student = studentWithSemesters.student, semester = semester, forceRefresh = forceRefresh ) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading student info $infoType started") - Status.SUCCESS -> { - val isFamily = infoType == StudentInfoView.Type.FAMILY - val isFirstGuardianEmpty = it.data?.firstGuardian == null - val isSecondGuardianEmpty = it.data?.secondGuardian == null - - if (it.data != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) { - Timber.i("Loading student info $infoType result: Success") - showCorrectData(it.data) - view?.run { - showContent(true) - showEmpty(false) - showErrorView(false) - } - analytics.logEvent("load_item", "type" to "student_info") - } else { - Timber.i("Loading student info $infoType result: No student or family info found") - view?.run { - showContent(!isViewEmpty) - showEmpty(isViewEmpty) - showErrorView(false) - } + } + .logResourceStatus("load student info $infoType") + .onResourceData { + val isFamily = infoType == StudentInfoView.Type.FAMILY + val isFirstGuardianEmpty = it?.firstGuardian == null + val isSecondGuardianEmpty = it?.secondGuardian == null + if (it != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) { + Timber.i("Loading student info $infoType result: Success") + showCorrectData(it) + view?.run { + showContent(true) + showEmpty(false) + showErrorView(false) + } + } else { + Timber.i("Loading student info $infoType result: No student or family info found") + view?.run { + showContent(!isViewEmpty) + showEmpty(isViewEmpty) + showErrorView(false) } } - Status.ERROR -> { - Timber.i("Loading student info $infoType result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .onResourceSuccess { + if (it != null) { + analytics.logEvent("load_item", "type" to "student_info") } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } } - }.launch() + .onResourceError(errorHandler::dispatch) + .launch() } private fun showCorrectData(studentInfo: StudentInfo) { 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 ec3ef7b0..dc6c8921 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.timetable -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode import io.github.wulkanowy.data.repositories.PreferencesRepository @@ -123,57 +123,47 @@ class TimetablePresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading timetable data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) timetableRepository.getTimetable( - student, semester, currentDate, currentDate, forceRefresh + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh, + timetableType = TimetableRepository.TimetableType.NORMAL ) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data?.lessons.isNullOrEmpty()) { - view?.run { - updateData(it.data!!.lessons) - enableSwipe(true) - showRefresh(true) - showErrorView(false) - showProgress(false) - showContent(true) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading timetable result: Success") - view?.apply { - updateData(it.data!!.lessons) - showEmpty(it.data.lessons.isEmpty()) - setDayHeaderMessage(it.data.headers.singleOrNull { header -> - header.date == currentDate - }?.content) - showErrorView(false) - showContent(it.data.lessons.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "timetable", - "items" to it.data!!.lessons.size - ) - } - Status.ERROR -> { - Timber.i("Loading timetable result: An exception occurred") - errorHandler.dispatch(it.error!!) + } + .logResourceStatus("load timetable data") + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.lessons.isNotEmpty()) + showEmpty(it.lessons.isEmpty()) + updateData(it.lessons) + setDayHeaderMessage(it.headers.singleOrNull { header -> header.date == currentDate }?.content) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "timetable", + "items" to it.lessons.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun updateData(lessons: List) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt index 742a8d59..d0a01b38 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -1,23 +1,14 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -137,39 +128,44 @@ class AdditionalLessonsPresenter @Inject constructor( private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { currentDate = date - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - timetableRepository.getTimetable(student, semester, date, date, forceRefresh, true) - }.onEach { - when (it.status) { - Status.LOADING -> Timber.i("Loading additional lessons data started") - Status.SUCCESS -> { - Timber.i("Loading additional lessons lessons result: Success") - view?.apply { - updateData(it.data!!.additional.sortedBy { item -> item.start }) - showEmpty(it.data.additional.isEmpty()) - showErrorView(false) - showContent(it.data.additional.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "additional_lessons", - "items" to it.data!!.additional.size - ) - } - Status.ERROR -> { - Timber.i("Loading additional lessons result: An exception occurred") - errorHandler.dispatch(it.error!!) + timetableRepository.getTimetable( + student = student, + semester = semester, + start = date, + end = date, + forceRefresh = forceRefresh, + refreshAdditional = true, + timetableType = TimetableRepository.TimetableType.ADDITIONAL + ) + } + .logResourceStatus("load additional lessons") + .onResourceData { + view?.apply { + updateData(it.additional.sortedBy { item -> item.start }) + showEmpty(it.additional.isEmpty()) + showErrorView(false) + showContent(it.additional.isNotEmpty()) } } - }.afterLoading { - view?.run { - hideRefresh() - showProgress(false) - enableSwipe(true) + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "additional_lessons", + "items" to it.additional.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index b75b42f8..16c51fd2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -1,22 +1,13 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.* import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.repositories.CompletedLessonsRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.AnalyticsHelper -import io.github.wulkanowy.utils.afterLoading -import io.github.wulkanowy.utils.capitalise -import io.github.wulkanowy.utils.flowWithResourceIn -import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday -import io.github.wulkanowy.utils.isHolidays -import io.github.wulkanowy.utils.nextOrSameSchoolDay -import io.github.wulkanowy.utils.nextSchoolDay -import io.github.wulkanowy.utils.previousSchoolDay -import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach @@ -111,51 +102,46 @@ class CompletedLessonsPresenter @Inject constructor( } private fun loadData(forceRefresh: Boolean = false) { - Timber.i("Loading completed lessons data started") - - flowWithResourceIn { + flatResourceFlow { val student = studentRepository.getCurrentStudent() val semester = semesterRepository.getCurrentSemester(student) - completedLessonsRepository.getCompletedLessons(student, semester, currentDate, currentDate, forceRefresh) - }.onEach { - when (it.status) { - Status.LOADING -> { - if (!it.data.isNullOrEmpty()) { - view?.run { - enableSwipe(true) - showRefresh(true) - showProgress(false) - showContent(true) - updateData(it.data.sortedBy { item -> item.number }) - } - } - } - Status.SUCCESS -> { - Timber.i("Loading completed lessons lessons result: Success") - view?.apply { - updateData(it.data!!.sortedBy { item -> item.number }) - showEmpty(it.data.isEmpty()) - showErrorView(false) - showContent(it.data.isNotEmpty()) - } - analytics.logEvent( - "load_data", - "type" to "completed_lessons", - "items" to it.data!!.size - ) - } - Status.ERROR -> { - Timber.i("Loading completed lessons result: An exception occurred") - completedLessonsErrorHandler.dispatch(it.error!!) + completedLessonsRepository.getCompletedLessons( + student = student, + semester = semester, + start = currentDate, + end = currentDate, + forceRefresh = forceRefresh + ) + } + .logResourceStatus("load completed lessons") + .mapResourceData { it.sortedBy { lesson -> lesson.number } } + .onResourceData { + view?.run { + enableSwipe(true) + showProgress(false) + showErrorView(false) + showContent(it.isNotEmpty()) + showEmpty(it.isEmpty()) + updateData(it) } } - }.afterLoading { - view?.run { - showRefresh(false) - showProgress(false) - enableSwipe(true) + .onResourceIntermediate { view?.showRefresh(true) } + .onResourceSuccess { + analytics.logEvent( + "load_data", + "type" to "completed_lessons", + "items" to it.size + ) } - }.launch() + .onResourceNotLoading { + view?.run { + enableSwipe(true) + showProgress(false) + showRefresh(false) + } + } + .onResourceError(errorHandler::dispatch) + .launch() } private fun showErrorViewOnError(message: String, error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index 2a40c8e4..dc2a7c6c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -1,14 +1,14 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey -import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -56,16 +56,15 @@ class TimetableWidgetConfigurePresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents(false) }.onEach { - when (it.status) { - Status.LOADING -> Timber.d("Timetable widget configure students data load") - Status.SUCCESS -> { + resourceFlow { studentRepository.getSavedStudents(false) }.onEach { + when (it) { + is Resource.Loading -> Timber.d("Timetable widget configure students data load") + is Resource.Success -> { val selectedStudentId = appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } ?: -1 - when { - it.data!!.isEmpty() -> view?.openLoginView() + it.data.isEmpty() -> view?.openLoginView() it.data.size == 1 && !isFromProvider -> { selectedStudent = it.data.single().student view?.showThemeDialog() @@ -73,7 +72,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( else -> view?.updateData(it.data, selectedStudentId) } } - Status.ERROR -> errorHandler.dispatch(it.error!!) + is Resource.Error -> errorHandler.dispatch(it.error) } }.launch() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 18eefc5d..664086bc 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -12,6 +12,7 @@ import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.enums.TimetableMode @@ -19,12 +20,12 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey import io.github.wulkanowy.utils.getCompatColor -import io.github.wulkanowy.utils.toFirstResult import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber @@ -118,7 +119,7 @@ class TimetableWidgetFactory( val semester = semesterRepository.getCurrentSemester(student) timetableRepository.getTimetable(student, semester, date, date, false) - .toFirstResult().data?.lessons.orEmpty() + .toFirstResult().dataOrNull?.lessons.orEmpty() .sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) .filter { if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt deleted file mode 100644 index 5dd28967..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.wulkanowy.utils - -import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -inline fun networkBoundResource( - mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, - crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline filterResult: (ResultType) -> ResultType = { it } -) = flow { - emit(Resource.loading()) - - val data = query().first() - emitAll(if (shouldFetch(data)) { - if (showSavedOnLoading) emit(Resource.loading(filterResult(data))) - - try { - val newData = fetch(data) - mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.success(filterResult(it)) } - } catch (throwable: Throwable) { - onFetchFailed(throwable) - query().map { Resource.error(throwable, filterResult(it)) } - } - } else { - query().map { Resource.success(filterResult(it)) } - }) -} - -@JvmName("networkBoundResourceWithMap") -inline fun networkBoundResource( - mutex: Mutex = Mutex(), - showSavedOnLoading: Boolean = true, - crossinline query: () -> Flow, - crossinline fetch: suspend (ResultType) -> RequestType, - crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { }, - crossinline shouldFetch: (ResultType) -> Boolean = { true }, - crossinline mapResult: (ResultType) -> T -) = flow { - emit(Resource.loading()) - - val data = query().first() - emitAll(if (shouldFetch(data)) { - if (showSavedOnLoading) emit(Resource.loading(mapResult(data))) - - try { - val newData = fetch(data) - mutex.withLock { saveFetchResult(query().first(), newData) } - query().map { Resource.success(mapResult(it)) } - } catch (throwable: Throwable) { - onFetchFailed(throwable) - query().map { Resource.error(throwable, mapResult(it)) } - } - } else { - query().map { Resource.success(mapResult(it)) } - }) -} - -fun flowWithResource(block: suspend () -> T) = flow { - emit(Resource.loading()) - emit(Resource.success(block())) -}.catch { emit(Resource.error(it)) } - -@OptIn(FlowPreview::class) -fun flowWithResourceIn(block: suspend () -> Flow>) = flow { - emit(Resource.loading()) - emitAll(block().filter { it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null) }) -}.catch { emit(Resource.error(it)) } - -fun Flow>.afterLoading(callback: () -> Unit) = onEach { - if (it.status != Status.LOADING) callback() -} - -suspend fun Flow>.toFirstResult() = filter { it.status != Status.LOADING }.first() - -suspend fun Flow>.waitForResult() = - takeWhile { it.status == Status.LOADING }.collect() diff --git a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt similarity index 96% rename from app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt rename to app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt index 57045a29..ea846a57 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/FlowUtilsKtTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/ResourceTest.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.utils +package io.github.wulkanowy.data import io.mockk.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -13,7 +13,7 @@ import org.junit.Test import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) -class FlowUtilsKtTest { +class ResourceTest { private val testScope = TestScope(UnconfinedTestDispatcher()) @@ -41,6 +41,7 @@ class FlowUtilsKtTest { // first networkBoundResource( + isResultEmpty = { false }, showSavedOnLoading = false, query = { repo.query() }, fetch = { @@ -55,6 +56,7 @@ class FlowUtilsKtTest { // second networkBoundResource( + isResultEmpty = { false }, showSavedOnLoading = false, query = { repo.query() }, fetch = { @@ -120,6 +122,7 @@ class FlowUtilsKtTest { // first networkBoundResource( + isResultEmpty = { false }, mutex = saveResultMutex, showSavedOnLoading = false, query = { repo.query() }, @@ -138,6 +141,7 @@ class FlowUtilsKtTest { // second networkBoundResource( + isResultEmpty = { false }, mutex = saveResultMutex, showSavedOnLoading = false, query = { repo.query() }, diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt index f3c7fba7..7d22f726 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -73,8 +70,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } @@ -97,8 +94,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { @@ -125,8 +122,8 @@ class AttendanceRepositoryTest { val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getAttendance(startDate, endDate, 1) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt index fa54522a..c28ea304 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -73,8 +70,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } @@ -97,8 +94,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { @@ -125,8 +122,8 @@ class CompletedLessonsRepositoryTest { val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt index 8bf4deee..e3790662 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -1,20 +1,17 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.ExamDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -74,8 +71,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } @@ -98,8 +95,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { @@ -126,8 +123,8 @@ class ExamRemoteTest { val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getExams(startDate, realEndDate, 1) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt index f7968bc4..e8d0b6c8 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -88,8 +90,8 @@ class GradeRepositoryTest { } // verify - assertEquals(null, res.error) - assertEquals(4, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(4, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(withArg { assertEquals(4, it.size) @@ -142,8 +144,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(4, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(4, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(withArg { assertEquals(3, it.size) @@ -184,8 +186,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(match { it.isEmpty() }) } coVerify { gradeDb.deleteAll(match { it.size == 1 }) } // ... here } @@ -214,8 +216,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(3, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(3, res.dataOrNull?.first?.size) coVerify { gradeDb.insertAll(match { it.size == 1 }) } // ... here coVerify { gradeDb.deleteAll(match { it.isEmpty() }) } } @@ -240,8 +242,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(3, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(3, res.dataOrNull?.first?.size) } @Test @@ -263,8 +265,8 @@ class GradeRepositoryTest { val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(0, res.data?.first?.size) + assertEquals(null, res.errorOrNull) + assertEquals(0, res.dataOrNull?.first?.size) } private fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String) = diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt index 6221b698..8e2f7c6e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -1,16 +1,18 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -81,11 +83,11 @@ class GradeStatisticsRepositoryTest { forceRefresh = true, ).toFirstResult() } - val items = res.data.orEmpty() + val items = res.dataOrNull.orEmpty() // verify - assertEquals(null, res.error) - assertEquals(2 + 1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2 + 1, res.dataOrNull?.size) assertEquals("", items[0].partial?.studentAverage) assertEquals("", items[1].partial?.studentAverage) assertEquals("", items[2].partial?.studentAverage) @@ -119,11 +121,11 @@ class GradeStatisticsRepositoryTest { forceRefresh = true, ).toFirstResult() } - val items = res.data.orEmpty() + val items = res.dataOrNull.orEmpty() // verify - assertEquals(null, res.error) - assertEquals(2 + 1, res.data?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2 + 1, res.dataOrNull?.size) assertEquals("3,00", items[0].partial?.studentAverage) assertEquals("1.0", items[1].partial?.studentAverage) assertEquals("5.0", items[2].partial?.studentAverage) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt index a89aad35..3225c3bd 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -1,17 +1,15 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -58,8 +56,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify(exactly = 0) { luckyNumberDb.insertAll(any()) } @@ -82,8 +80,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { @@ -112,8 +110,8 @@ class LuckyNumberRemoteTest { val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(luckyNumber.number, res.data?.luckyNumber) + assertEquals(null, res.errorOrNull) + assertEquals(luckyNumber.number, res.dataOrNull?.luckyNumber) coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { luckyNumberDb.load(1, date) } coVerify { diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt index f21fc178..2a5d2e2b 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories import android.content.Context -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.errorOrNull +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk @@ -15,7 +17,8 @@ import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.MessageDetails import io.github.wulkanowy.sdk.pojo.Sender import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult +import io.github.wulkanowy.utils.Status +import io.github.wulkanowy.utils.status import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK @@ -102,7 +105,7 @@ class MessageRepositoryTest { folder = MessageFolder.RECEIVED, forceRefresh = true, notify = true, // all new messages will be marked as not notified - ).toFirstResult().data.orEmpty() + ).toFirstResult().dataOrNull.orEmpty() coVerify(exactly = 1) { messageDb.deleteAll(emptyList()) } coVerify(exactly = 1) { messageDb.insertAll(emptyList()) } @@ -133,7 +136,7 @@ class MessageRepositoryTest { folder = MessageFolder.RECEIVED, forceRefresh = true, notify = false, - ).toFirstResult().data.orEmpty() + ).toFirstResult().dataOrNull.orEmpty() coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList()) }) } coVerify { @@ -165,9 +168,9 @@ class MessageRepositoryTest { val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } - assertEquals(null, res.error) + assertEquals(null, res.errorOrNull) assertEquals(Status.SUCCESS, res.status) - assertEquals("Test", res.data!!.message.content) + assertEquals("Test", res.dataOrNull!!.message.content) } @Test @@ -197,9 +200,9 @@ class MessageRepositoryTest { val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } - assertEquals(null, res.error) + assertEquals(null, res.errorOrNull) assertEquals(Status.SUCCESS, res.status) - assertEquals("Test", res.data!!.message.content) + assertEquals("Test", res.dataOrNull!!.message.content) coVerify { messageDb.updateAll(listOf(testMessageWithContent)) } } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt index c5a7756f..b9a958d4 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -1,21 +1,18 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Device import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert @@ -69,8 +66,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(2, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } @@ -93,8 +90,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(2, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(2, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { @@ -121,8 +118,8 @@ class MobileDeviceRepositoryTest { val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } // verify - Assert.assertEquals(null, res.error) - Assert.assertEquals(1, res.data?.size) + Assert.assertEquals(null, res.errorOrNull) + Assert.assertEquals(1, res.dataOrNull?.size) coVerify { sdk.getRegisteredDevices() } coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt index adb4f33a..e56aaa5d 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -1,24 +1,21 @@ package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableHeaderDao +import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.TimetableFull import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.AutoRefreshHelper -import io.github.wulkanowy.utils.toFirstResult -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.SpyK -import io.mockk.just import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -96,8 +93,8 @@ class TimetableRepositoryTest { val res = runBlocking { timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() } // verify - assertEquals(null, res.error) - assertEquals(2, res.data?.lessons?.size) + assertEquals(null, res.errorOrNull) + assertEquals(2, res.dataOrNull!!.lessons.size) coVerify { sdk.getTimetableFull(startDate, endDate) } coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } coVerify { timetableDb.insertAll(match { it.isEmpty() }) } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index 5e8c4c11..a6ecdc26 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -1,16 +1,18 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.Resource -import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.Status +import io.github.wulkanowy.utils.status import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every @@ -29,7 +31,7 @@ import java.time.LocalDate.of class GradeAverageProviderTest { - private suspend fun Flow>.getResult() = toList()[1].data!! + private suspend fun Flow>.getResult() = toList()[1].dataOrNull!! @MockK lateinit var preferencesRepository: PreferencesRepository @@ -144,7 +146,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { noWeightGrades to noWeightGradesSummary } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { noWeightGrades to noWeightGradesSummary } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -156,7 +164,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns true every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { noWeightGrades to noWeightGradesArithmeticSummary } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { noWeightGrades to noWeightGradesArithmeticSummary } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -170,27 +184,27 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGradeWithModifier to secondSummariesWithModifier)) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Loading()) + emit(Resource.Intermediate(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus + assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus } @Test @@ -201,27 +215,27 @@ class GradeAverageProviderTest { coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flow { - emit(Resource.loading()) + emit(Resource.Loading()) delay(1000) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flow { - emit(Resource.loading()) - emit(Resource.success(secondGradeWithModifier to secondSummariesWithModifier)) + emit(Resource.Loading()) + emit(Resource.Success(secondGradeWithModifier to secondSummariesWithModifier)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.SUCCESS, status) - assertEquals(1, data!!.size) + assertEquals(1, dataOrNull?.size) } - assertEquals(3.5, items[1].data?.single { it.subject == "Język polski" }!!.average, .0) // from details and after set custom plus/minus + assertEquals(3.5, items[1].dataOrNull?.single { it.subject == "Język polski" }?.average ?: 0.0, .0) // from details and after set custom plus/minus } @Test @@ -230,12 +244,26 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flowWithResource { - listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf(getSummary(semesters[2].semesterId, "Język polski", 2.5)) + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + false + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns resourceFlow { + listOf(getGrade(semesters[2].semesterId, "Język polski", .0, .0, .0)) to listOf( + getSummary(semesters[2].semesterId, "Język polski", 2.5) + ) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + false + ).getResult() + } assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) } @@ -246,10 +274,28 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], false) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } - coEvery { gradeRepository.getGrades(student, semesters[2], false) } returns flowWithResource { emptyList() to listOf(getSummary(24, "Język polski", .0))} + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + false + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + false + ) + } returns resourceFlow { emptyList() to listOf(getSummary(24, "Język polski", .0)) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, false).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + false + ).getResult() + } assertEquals(3.49, items.single { it.subject == "Język polski" }.average, .0) } @@ -260,10 +306,28 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { emptyList() to emptyList() } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList() to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { emptyList() to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { emptyList() to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(0, items.size) } @@ -274,7 +338,13 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -292,7 +362,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -310,7 +386,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -328,7 +410,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGradeWithModifier to secondSummariesWithModifier } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGradeWithModifier to secondSummariesWithModifier } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -340,7 +428,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -354,7 +448,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ONE_SEMESTER - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } @@ -368,7 +468,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).getResult() } @@ -384,29 +490,29 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to firstSummaries)) - emit(Resource.success(firstGrades to firstSummaries)) + emit(Resource.Loading()) + emit(Resource.Intermediate(firstGrades to firstSummaries)) + emit(Resource.Success(firstGrades to firstSummaries)) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.5, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5 - assertEquals(3.5, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summary): 3,5 + assertEquals(3.5, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summary): 3,5 } @Test @@ -414,13 +520,13 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.gradeAverageForceCalc } returns false every { preferencesRepository.isOptionalArithmeticAverage } returns false - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 3.0), getSummary(22, "Fizyka", 3.5) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 3.5), getSummary(22, "Fizyka", 4.0) @@ -440,46 +546,62 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageForceCalc } returns false coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) - ))) - emit(Resource.success(firstGrades to listOf( - getSummary(22, "Matematyka", 3.0), - getSummary(22, "Fizyka", 3.5) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ) + ) + ) + emit( + Resource.Success( + firstGrades to listOf( + getSummary(22, "Matematyka", 3.0), + getSummary(22, "Fizyka", 3.5) + ) + ) + ) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) - ))) - emit(Resource.success(secondGrades to listOf( - getSummary(22, "Matematyka", 3.5), - getSummary(22, "Fizyka", 4.0) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ) + ) + ) + emit( + Resource.Success( + secondGrades to listOf( + getSummary(22, "Matematyka", 3.5), + getSummary(22, "Fizyka", 4.0) + ) + ) + ) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.25, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 - assertEquals(3.75, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25 + assertEquals(3.75, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75 } @Test @@ -487,8 +609,14 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageForceCalc } returns true every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", 1.1), getSummary(22, "Fizyka", 7.26) @@ -527,7 +655,7 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns flowWithResource { + } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", .0), getSummary(22, "Fizyka", .0) @@ -539,7 +667,7 @@ class GradeAverageProviderTest { semesters[2], true ) - } returns flowWithResource { + } returns resourceFlow { secondGrades to listOf( getSummary(22, "Matematyka", .0), getSummary(22, "Fizyka", .0) @@ -566,40 +694,48 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(firstGrades to firstSummaries)) - emit(Resource.success(firstGrades to firstSummaries)) + emit(Resource.Loading()) + emit(Resource.Intermediate(firstGrades to firstSummaries)) + emit(Resource.Success(firstGrades to firstSummaries)) } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flow { - emit(Resource.loading()) - emit(Resource.loading(secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) - ))) - emit(Resource.success(secondGrades to listOf( - getSummary(22, "Matematyka", 1.1), - getSummary(22, "Fizyka", 7.26) - ))) + emit(Resource.Loading()) + emit( + Resource.Intermediate( + secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ) + ) + ) + emit( + Resource.Success( + secondGrades to listOf( + getSummary(22, "Matematyka", 1.1), + getSummary(22, "Fizyka", 7.26) + ) + ) + ) } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).toList() } with(items[0]) { assertEquals(Status.LOADING, status) - assertEquals(null, data) + assertEquals(null, dataOrNull) } with(items[1]) { assertEquals(Status.LOADING, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } with(items[2]) { assertEquals(Status.SUCCESS, status) - assertEquals(2, data!!.size) + assertEquals(2, dataOrNull?.size) } - assertEquals(2, items[2].data!!.size) - assertEquals(3.0, items[2].data!!.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items[2].data!!.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals(2, items[2].dataOrNull?.size) + assertEquals(3.0, items[2].dataOrNull?.single { it.subject == "Matematyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 2,5 → 3,0 + assertEquals(3.25, items[2].dataOrNull?.single { it.subject == "Fizyka" }?.average ?: 0.0, .0) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -614,14 +750,14 @@ class GradeAverageProviderTest { semesters[1], true ) - } returns flowWithResource { firstGrades to emptyList() } + } returns resourceFlow { firstGrades to emptyList() } coEvery { gradeRepository.getGrades( student, semesters[2], true ) - } returns flowWithResource { secondGrades to emptyList() } + } returns resourceFlow { secondGrades to emptyList() } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( @@ -646,14 +782,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to emptyList() } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to emptyList() } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to emptyList() } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from details): 3,5 + 2,5 → 3,0 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -662,12 +824,12 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { firstGrades to listOf( getSummary(22, "Matematyka", 4.0) ) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { secondGrades to listOf( getSummary(23, "Matematyka", 3.0) ) @@ -686,14 +848,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries.dropLast(1) } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 - assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05 + assertEquals( + 3.4, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals( + 3.05, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // 3,1 (from summary) + 3,0 (from details) → 3,05 } @Test @@ -702,14 +890,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4 - assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45 + assertEquals( + 3.4, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from summaries): 3,9 + 2,9 → 3,4 + assertEquals( + 3.45, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // 3,5 (from details) + 3,4 (from summary) → 3,45 } @Test @@ -718,14 +932,40 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { firstGrades to firstSummaries.dropLast(1) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { secondGrades to secondSummaries } + coEvery { + gradeRepository.getGrades( + student, + semesters[1], + true + ) + } returns resourceFlow { firstGrades to firstSummaries.dropLast(1) } + coEvery { + gradeRepository.getGrades( + student, + semesters[2], + true + ) + } returns resourceFlow { secondGrades to secondSummaries } - val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).getResult() } + val items = runBlocking { + gradeAverageProvider.getGradesDetailsWithAverage( + student, + semesters[2].semesterId, + true + ).getResult() + } assertEquals(2, items.size) - assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0 - assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5 + 3,0 → 3,25 + assertEquals( + 3.0, + items.single { it.subject == "Matematyka" }.average, + .0 + ) // (from details): 3,5 + 2,5 → 3,0 + assertEquals( + 3.25, + items.single { it.subject == "Fizyka" }.average, + .0 + ) // (from details): 3,5 + 3,0 → 3,25 } @Test @@ -734,7 +974,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS every { preferencesRepository.isOptionalArithmeticAverage } returns false - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -746,7 +986,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -765,7 +1005,7 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -777,7 +1017,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -802,7 +1042,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.BOTH_SEMESTERS coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -814,7 +1054,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -839,7 +1079,7 @@ class GradeAverageProviderTest { every { preferencesRepository.gradeAverageMode } returns GradeAverageMode.ALL_YEAR coEvery { semesterRepository.getSemesters(student) } returns semesters - coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns resourceFlow { listOf( getGrade(22, "Fizyka", 5.0, weight = 2.0), getGrade(22, "Fizyka", 6.0, weight = 2.0), @@ -851,7 +1091,7 @@ class GradeAverageProviderTest { getGrade(22, "Fizyka", 6.0, weight = 2.0) ) to listOf(getSummary(semesterId = 22, subject = "Fizyka", average = .0)) } - coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { + coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns resourceFlow { listOf( getGrade(23, "Fizyka", 5.0, weight = 1.0), getGrade(23, "Fizyka", 5.0, weight = 2.0), @@ -881,9 +1121,9 @@ class GradeAverageProviderTest { every { preferencesRepository.isOptionalArithmeticAverage } returns false coEvery { gradeRepository.getGrades(student, semesters[1], true) } returns - flowWithResource { firstGrades to firstSummaries } + resourceFlow { firstGrades to firstSummaries } coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns - flowWithResource { listOf() to firstSummaries } + resourceFlow { listOf() to firstSummaries } val items = runBlocking { gradeAverageProvider.getGradesDetailsWithAverage( diff --git a/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt b/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt new file mode 100644 index 00000000..60df1db0 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/utils/ResourceUtils.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.Resource + +enum class Status { + LOADING, SUCCESS, ERROR +} + +val Resource.status + get() = when (this) { + is Resource.Error -> Status.ERROR + is Resource.Loading -> Status.LOADING + is Resource.Success -> Status.SUCCESS + } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0f7639f..d7e66b5c 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.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882e..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@"