Resource refactor (#1589)

This commit is contained in:
Michael 2022-03-27 15:33:32 +02:00 committed by GitHub
parent 042b66ca5c
commit 20dde6e896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 2419 additions and 2361 deletions

View File

@ -1,23 +1,173 @@
package io.github.wulkanowy.data
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
sealed class Resource<T> {
open class Loading<T> : Resource<T>()
data class Intermediate<T>(val data: T) : Loading<T>()
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val error: Throwable) : Resource<T>()
}
val <T> Resource<T>.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> null
is Resource.Error -> null
}
fun <T> error(error: Throwable?, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, error)
val <T> Resource<T>.errorOrNull: Throwable?
get() = when (this) {
is Resource.Error -> this.error
else -> null
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
fun <T> resourceFlow(block: suspend () -> T) = flow {
emit(Resource.Loading())
emit(Resource.Success(block()))
}.catch { emit(Resource.Error(it)) }
fun <T> flatResourceFlow(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.Loading())
emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading })
}.catch { emit(Resource.Error(it)) }
fun <T, U> Resource<T>.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 <T> Flow<Resource<T>>.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 <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
it.mapData(block)
}
fun <T> Flow<Resource<T>>.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 <T> Flow<Resource<T>>.onResourceLoading(block: suspend () -> Unit) = onEach {
if (it is Resource.Loading) {
block()
}
}
fun <T> Flow<Resource<T>>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Intermediate) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Success) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
if (it is Resource.Error) {
block(it.error)
}
}
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
if (it !is Resource.Loading) {
block()
}
}
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loading }.first()
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
inline fun <ResultType, RequestType> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (ResultType) -> Boolean,
crossinline query: () -> Flow<ResultType>,
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 <ResultType, RequestType, T> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (T) -> Boolean,
crossinline query: () -> Flow<ResultType>,
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)) }
})
}

View File

@ -33,6 +33,10 @@ abstract class StudentDao {
@Query("SELECT * FROM Students")
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
@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)

View File

@ -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 },

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 = {

View File

@ -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<Resource<List<Message>>> = 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<Resource<MessageWithAttachment?>> = 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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 = {

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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<T : BaseView>(
@ -37,7 +30,10 @@ open class BasePresenter<T : BaseView>(
}
fun onExpiredLoginSelected() {
flowWithResource {
Timber.i("Attempt to switch the student after the session expires")
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(false)
studentRepository.logoutStudent(student)
@ -46,20 +42,17 @@ open class BasePresenter<T : BaseView>(
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 <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
childrenJobs[individualJobTag]?.cancel()

View File

@ -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()
}
}

View File

@ -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 {
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()
}
}
}

View File

@ -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")
}

View File

@ -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,26 +46,16 @@ 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 -> {
resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) }
.logResourceStatus("loading account details view")
.onResourceLoading {
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
.onResourceSuccess {
studentWithSemesters = it
view?.run {
showAccountData(studentWithSemesters!!.student)
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
@ -78,13 +63,8 @@ class AccountDetailsPresenter @Inject constructor(
showErrorView(false)
}
}
Status.ERROR -> {
Timber.i("Loading account details view result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}
.afterLoading { view?.showProgress(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!!)
}
}
}.afterLoading {
.onResourceNotLoading {
if (studentWithSemesters?.student?.isCurrent == true) {
view?.popViewToMain()
} else {
view?.popViewToAccounts()
}
}.launch("logout")
}
.onResourceError(errorHandler::dispatch)
.launch("logout")
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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")
}
}

View File

@ -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")
}

View File

@ -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 }
}
.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)
showRefresh(true)
showProgress(false)
showErrorView(false)
showEmpty(filteredAttendance.isEmpty())
showContent(filteredAttendance.isNotEmpty())
updateData(filteredAttendance.sortedBy { item -> item.number })
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading attendance result: Success")
val filteredAttendance = if (prefRepository.isShowPresent) {
it.data.orEmpty()
} else {
it.data?.filter { item -> !item.presence }.orEmpty()
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
val anyExcusables = it.any { it.isExcusableOrNotExcused }
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
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
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading attendance result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun excuseAbsence(reason: String?, toExcuseList: List<Attendance>) {
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()
}
}

View File

@ -74,7 +74,7 @@ class AttendanceSummaryFragment :
binding.attendanceSummarySubjectsContainer.elevation = requireContext().dpToPx(1f)
}
override fun updateSubjects(data: ArrayList<String>) {
override fun updateSubjects(data: Collection<String>) {
with(subjectsAdapter) {
clear()
addAll(data)

View File

@ -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()) {
}
.logResourceStatus("load attendance summary")
.mapResourceData(this::sortItems)
.onResourceData {
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showContent(true)
showErrorView(false)
updateDataSet(sortItems(it.data))
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateDataSet(it)
}
}
}
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))
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "attendance_summary",
"items" to it.data!!.size,
"items" to it.size,
"item_id" to subjectId
)
}
Status.ERROR -> {
Timber.i("Loading attendance summary result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
showRefresh(false)
enableSwipe(true)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun sortItems(items: List<AttendanceSummary>) = 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")
}
.logResourceStatus("load attendance summary subjects")
.onResourceData {
subjects = it
view?.run {
view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name }))
view?.updateSubjects(it.map { subject -> subject.name }.toList())
showSubjects(true)
}
}
Status.ERROR -> {
Timber.i("Loading attendance summary subjects result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("subjects")
.onResourceError(errorHandler::dispatch)
.launch("subjects")
}
}

View File

@ -25,7 +25,7 @@ interface AttendanceSummaryView : BaseView {
fun updateDataSet(data: List<AttendanceSummary>)
fun updateSubjects(data: ArrayList<String>)
fun updateSubjects(data: Collection<String>)
fun showSubjects(show: Boolean)

View File

@ -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()) {
}
.logResourceStatus("load conference data")
.mapResourceData { it.sortedByDescending { conference -> conference.date } }
.onResourceData {
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)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "conferences",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading conference result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
}

View File

@ -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
@ -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,13 +293,13 @@ 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()
}
.mapResourceData { (details, _) ->
val filteredSubjectWithGrades = details
.filter { it.date >= LocalDate.now().minusDays(7) }
.groupBy { it.subject }
.mapValues { entry ->
@ -318,30 +311,26 @@ class DashboardPresenter @Inject constructor(
.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 -> {
filteredSubjectWithGrades
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard grades data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
subjectWithGrades = it.dataOrNull,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
), forceRefresh
)
if (!it.data.isNullOrEmpty()) {
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.GRADES
}
}
Status.SUCCESS -> {
is Resource.Success -> {
Timber.i("Loading dashboard grades result: Success")
updateData(
DashboardItem.Grades(
@ -351,17 +340,18 @@ class DashboardPresenter @Inject constructor(
forceRefresh
)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading dashboard grades result: An exception occurred")
errorHandler.dispatch(it.error!!)
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.status) {
Status.LOADING -> {
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard lessons data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Lessons(it.data, isLoading = true),
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
forceRefresh
)
if (!it.data?.lessons.isNullOrEmpty()) {
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.LESSONS
}
}
Status.SUCCESS -> {
is Resource.Success -> {
Timber.i("Loading dashboard lessons result: Success")
updateData(
DashboardItem.Lessons(it.data), forceRefresh
)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error!!)
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 ->
}
.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 -> {
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(it.data ?: emptyList(), isLoading = true),
DashboardItem.Homework(data, isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
if (data.isNotEmpty()) {
firstLoadedItemList += DashboardItem.Type.HOMEWORK
}
}
Status.SUCCESS -> {
is Resource.Success -> {
Timber.i("Loading dashboard homework result: Success")
updateData(DashboardItem.Homework(it.data ?: emptyList()), forceRefresh)
updateData(DashboardItem.Homework(it.data), forceRefresh)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading dashboard homework result: An exception occurred")
errorHandler.dispatch(it.error!!)
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 -> {
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard announcements data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true),
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
}
}
Status.SUCCESS -> {
is Resource.Success -> {
Timber.i("Loading dashboard announcements result: Success")
updateData(DashboardItem.Announcements(it.data ?: emptyList()), forceRefresh)
updateData(DashboardItem.Announcements(it.data), forceRefresh)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading dashboard announcements result: An exception occurred")
errorHandler.dispatch(it.error!!)
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 -> {
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard conferences data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.CONFERENCES
}
}
Status.SUCCESS -> {
is Resource.Success -> {
Timber.i("Loading dashboard conferences result: Success")
updateData(DashboardItem.Conferences(it.data ?: emptyList()), forceRefresh)
updateData(DashboardItem.Conferences(it.data), forceRefresh)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading dashboard conferences result: An exception occurred")
errorHandler.dispatch(it.error!!)
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)

View File

@ -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 }}")
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)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it.error!!)
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")
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)
}
Status.ERROR -> {
is Resource.Error -> {
Timber.i("Loading last log file result: An exception occurred")
errorHandler.dispatch(it.error!!)
errorHandler.dispatch(it.error)
}
}
}.launch("file")
}
.launch("file")
}
}

View File

@ -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 {
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}.launch("holidays")
}
.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()) {
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)
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())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "exam",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading exam result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.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")
)
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
.logResourceStatus("load grade data")
.onResourceData {
val current = it.getCurrentOrLast()
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
schoolYear = current.schoolYear
semesters = it.data.filter { semester -> semester.diaryId == current.diaryId }
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
view?.setCurrentSemesterName(current.semesterName, schoolYear)
view?.run {
Timber.i("Loading grade result: Attempt load index $currentPageIndex")
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
showErrorView(false)
showSemesterSwitch(true)
}
}
Status.ERROR -> {
Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch()
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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()

View File

@ -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")
}
.logResourceStatus("load grade details")
.onResourceData {
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!!)
updateMarkAsDoneButton()
val items = createGradeItems(it.data)
view?.run {
showEmpty(items.isEmpty())
showErrorView(false)
showContent(items.isNotEmpty())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateNewGradesAmount(it)
updateMarkAsDoneButton()
updateData(
data = items,
data = createGradeItems(it),
expandMode = preferencesRepository.gradeExpandMode,
gradeColorTheme = preferencesRepository.gradeColorTheme
preferencesRepository.gradeColorTheme
)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "grade_details",
"items" to it.data.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading grade details result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
}
}.catch {
}
.catch {
errorHandler.dispatch(it)
view?.notifyParentDataLoaded(semesterId)
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun updateNewGradesAmount(grades: List<GradeSubject>) {
@ -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")
}
}

View File

@ -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")
}
.logResourceStatus("load grade stats subjects")
.onResourceData {
subjects = it
view?.run {
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
updateSubjects(
data = it.data.map { subject -> subject.name },
selectedIndex = it.data.indexOfFirst { subject ->
data = it.map { subject -> subject.name },
selectedIndex = it.indexOfFirst { subject ->
subject.name == currentSubjectName
},
)
}
}
Status.ERROR -> {
Timber.i("Loading grade stats subjects result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.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) {
}
.logResourceStatus("load grade stats data")
.mapResourceData {
val isNoContent = checkIsNoContent(it, type)
if (isNoContent) emptyList() else it
}
.onResourceData {
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)
showEmpty(it.isEmpty())
updateData(
newItems = if (isNoContent) emptyList() else it.data,
newItems = it,
newTheme = preferencesRepository.gradeColorTheme,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList
)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "grade_statistics",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading grade stats result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
enableSwipe(true)
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
}
}
}

View File

@ -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")
}
.logResourceStatus("load grade summary", showData = true)
.mapResourceData { createGradeSummaryItems(it) }
.onResourceData {
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)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "grade_summary",
"items" to it.data.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading grade summary result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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 {
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}.launch("holidays")
}
.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()) {
homeworkRepository.getHomework(
student = student,
semester = semester,
start = currentDate,
end = currentDate,
forceRefresh = forceRefresh
)
}
.logResourceStatus("loading homework")
.mapResourceData { createHomeworkItem(it) }
.onResourceData {
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())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "homework",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading homework result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@ -159,7 +149,8 @@ class HomeworkPresenter @Inject constructor(
private fun createHomeworkItem(items: List<Homework>): List<HomeworkItem<*>> {
return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam ->
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")
)
}
}
}

View File

@ -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")
}
.logResourceStatus("homework insert")
.onResourceSuccess {
view?.run {
showSuccessMessage()
closeDialog()
}
}
Status.ERROR -> {
Timber.i("Homework insert result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("add_homework")
.onResourceError(errorHandler::dispatch)
.launch("add_homework")
}
}

View File

@ -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")
resourceFlow { homeworkRepository.deleteHomework(homework) }
.logResourceStatus("homework delete")
.onResourceSuccess {
view?.run {
showMessage(homeworkDeleteSuccess)
closeDialog()
}
}
Status.ERROR -> {
Timber.i("Homework delete result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.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")
resourceFlow { homeworkRepository.toggleDone(homework) }
.logResourceStatus("homework details update")
.onResourceSuccess {
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!!)
}
}
}.launch("toggle")
.onResourceError(errorHandler::dispatch)
.launch("toggle")
}
}

View File

@ -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")
resourceFlow { getStudentsAppropriatesToLoginType() }
.logResourceStatus("login")
.onEach {
when (it) {
is Resource.Loading -> view?.run {
hideSoftKeyboard()
showProgress(true)
showContent(false)
}
Status.SUCCESS -> {
Timber.i("Login result: Success")
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(
@ -155,17 +156,16 @@ class LoginAdvancedPresenter @Inject constructor(
else -> view?.navigateToStudentSelect(it.data)
}
}
Status.ERROR -> {
Timber.i("Login result: An exception occurred")
is Resource.Error -> {
analytics.logEvent(
"registration_form",
"success" to false, "students" to -1,
"error" to it.error!!.message.ifNullOrBlank { "No message" }
"error" to it.error.message.ifNullOrBlank { "No message" }
)
loginErrorHandler.dispatch(it.error)
}
}
}.afterLoading {
}.onResourceNotLoading {
view?.apply {
showProgress(false)
showContent(true)

View File

@ -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")
}
.onResourceSuccess {
when (it.size) {
0 -> view?.navigateToSymbol(LoginData(email, password, host))
else -> view?.navigateToStudentSelect(it)
}
analytics.logEvent(
"registration_form",
"success" to true,
"students" to it.data!!.size,
"students" to it.size,
"scrapperBaseUrl" to host,
"error" to "No error"
)
when (it.data.size) {
0 -> view?.navigateToSymbol(LoginData(email, password, host))
else -> view?.navigateToStudentSelect(it.data)
}
.onResourceNotLoading {
view?.apply {
showProgress(false)
showContent(true)
}
}
Status.ERROR -> {
Timber.i("Login result: An exception occurred")
.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.error!!.message.ifNullOrBlank { "No message" })
loginErrorHandler.dispatch(it.error)
lastError = it.error
view?.showContact(true)
"error" to it.message.ifNullOrBlank { "No message" }
)
}
}
}.afterLoading {
view?.apply {
showProgress(false)
showContent(true)
}
}.launch("login")
.launch("login")
}
fun onFaqClick() {

View File

@ -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")
}

View File

@ -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<StudentWithSemesters>) {
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<StudentWithSemesters>) {
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)
}
}

View File

@ -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)

View File

@ -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,28 +31,20 @@ 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")
}
.logResourceStatus("load lucky number")
.onResourceData {
if (it != null) {
view?.apply {
updateData(it.data)
updateData(it)
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)
@ -63,18 +52,24 @@ class LuckyNumberPresenter @Inject constructor(
}
}
}
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 {
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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 {
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear)
reloadNavigation()
}.launch("holidays")
}
.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")
emitAll(
luckyNumberRepository.getLuckyNumberHistory(
student = student,
start = currentDate.monday,
end = currentDate.sunday
)
)
}
.onEach {
if (!it.isNullOrEmpty()) {
view?.apply {
updateData(it.data!!.first())
updateData(it)
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)
}
}
}
Status.ERROR -> {
Timber.i("Loading lucky number history result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
view?.run {
showProgress(false)
}
}.launch()
}
analytics.logEvent(
"load_items",
"type" to "lucky_number_history",
)
}
.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")
)
}
}

View File

@ -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()
}

View File

@ -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<LuckyNumber?>(null)
}
} catch (e: Exception) {
if (e.cause !is NoCurrentStudentException) {
Timber.e(e, "An error has occurred in lucky number provider")
}
null
Resource.Error(e)
}
}

View File

@ -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
resourceFlow { studentRepository.getSavedStudents(false) }
.logResourceStatus("load student avatar")
.onResourceSuccess {
studentsWitSemesters = it
showCurrentStudentAvatar()
}
Status.ERROR -> {
Timber.i("Loading student avatar result: An exception occurred")
errorHandler.dispatch(resource.error!!)
}
}
}.launch("avatar")
.onResourceError(errorHandler::dispatch)
.launch("avatar")
}
fun onViewChange(destinationView: BaseView) {

View File

@ -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,28 +50,21 @@ 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
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.data)
setMessageWithAttachment(it)
showContent(true)
initOptions()
}
analytics.logEvent(
"load_item",
"type" to "message_preview",
"length" to it.data.message.content.length
)
} else {
view?.run {
showMessage(messageNotExists)
@ -82,15 +72,21 @@ class MessagePreviewPresenter @Inject constructor(
}
}
}
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 {
Timber.i("Delete message ${message?.id}")
presenterScope.launch {
runCatching {
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")
}
.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) {

View File

@ -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) {
setSubject(
when (reply) {
true -> "Re: "
else -> "FW: "
} + message.subject)
} + 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,25 +123,34 @@ 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")
}
.logResourceStatus("load recipients")
.onEach {
when (it) {
is Resource.Loading -> view?.run {
showProgress(true)
showContent(false)
}
Status.SUCCESS -> it.data!!.let { (reportingUnit, recipientChips, selectedRecipientChips) ->
is Resource.Success -> it.data.let { (reportingUnit, recipientChips, selectedRecipientChips) ->
view?.run {
if (reportingUnit != null) {
setReportingUnit(reportingUnit)
setRecipients(recipientChips)
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(selectedRecipientChips)
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
selectedRecipientChips
)
showContent(true)
} else {
Timber.i("Loading recipients result: Can't find the reporting unit")
@ -151,32 +158,29 @@ class SendMessagePresenter @Inject constructor(
}
}
}
Status.ERROR -> {
Timber.i("Loading recipients result: An exception occurred")
is Resource.Error -> {
view?.showContent(true)
errorHandler.dispatch(it.error!!)
errorHandler.dispatch(it.error)
}
}
}.afterLoading {
}.onResourceNotLoading {
view?.run { showProgress(false) }
}.launch()
}
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
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)
}

View File

@ -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,14 +100,16 @@ 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()) {
}
.logResourceStatus("load $folder message")
.onEach {
when (it) {
is Resource.Intermediate -> {
if (it.data.isNotEmpty()) {
view?.run {
enableSwipe(true)
showErrorView(false)
@ -133,15 +128,23 @@ class MessageTabPresenter @Inject constructor(
val messageItemsWithHeader =
listOf(MessageTabDataItem.Header) + messageItems
updateData(messageItemsWithHeader, folder.id == MessageFolder.SENT.id)
updateData(
messageItemsWithHeader,
folder.id == MessageFolder.SENT.id
)
notifyParentDataLoaded()
}
}
}
Status.SUCCESS -> {
Timber.i("Loading $folder message result: Success")
messages = it.data!!
updateData(getFilteredData(lastSearchQuery, onlyUnread, onlyWithAttachments))
is Resource.Success -> {
messages = it.data
updateData(
getFilteredData(
lastSearchQuery,
onlyUnread,
onlyWithAttachments
)
)
analytics.logEvent(
"load_data",
"type" to "messages",
@ -149,22 +152,23 @@ class MessageTabPresenter @Inject constructor(
"folder" to folder.name
)
}
Status.ERROR -> {
Timber.i("Loading $folder message result: An exception occurred")
errorHandler.dispatch(it.error!!)
else -> {}
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded()
}
}.catch {
}
.onResourceError(errorHandler::dispatch)
.catch {
errorHandler.dispatch(it)
view?.notifyParentDataLoaded()
}.launch()
}
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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()) {
}
.logResourceStatus("load mobile devices data")
.onResourceData {
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)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "devices",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading mobile devices result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.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")
}
.logResourceStatus("unregister device")
.onResourceSuccess {
view?.run {
showProgress(false)
enableSwipe(true)
}
}
Status.ERROR -> {
Timber.i("Unregister device result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("unregister")
.onResourceError(errorHandler::dispatch)
.launch("unregister")
}
}

View File

@ -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")
}
.logResourceStatus("load mobile device registration")
.onResourceData {
view?.run {
updateData(it.data!!)
updateData(it)
showContent()
}
analytics.logEvent("device_register", "symbol" to it.data!!.token.substring(0, 3))
}
Status.ERROR -> {
Timber.i("Mobile device registration result: An exception occurred")
.onResourceSuccess {
analytics.logEvent(
"device_register",
"symbol" to it.token.substring(0, 3)
)
}
.onResourceNotLoading { view?.hideLoading() }
.onResourceError {
view?.closeDialog()
errorHandler.dispatch(it.error!!)
errorHandler.dispatch(it)
}
}
}.afterLoading {
view?.hideLoading()
}.launch()
.launch()
}
}

View File

@ -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()) {
}
.logResourceStatus("load note data")
.mapResourceData { it.sortedByDescending { note -> note.date } }
.onResourceData {
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())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "note",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading note result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.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)
}
}
}

View File

@ -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 {
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded()
}
}.catch {
}
.onResourceError(errorHandler::dispatch)
.catch {
errorHandler.dispatch(it)
view?.notifyParentDataLoaded()
}.launch()
}
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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")
}
.logResourceStatus("load teachers data")
.onResourceData {
view?.run {
updateData(it.data!!.filter { item -> item.name.isNotBlank() })
showContent(it.data.isNotEmpty())
showEmpty(it.data.isEmpty())
updateData(it.filter { item -> item.name.isNotBlank() })
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
showErrorView(false)
}
}
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "teachers",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading teachers result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded()
}
}.catch {
}
.onResourceError(errorHandler::dispatch)
.catch {
errorHandler.dispatch(it)
view?.notifyParentDataLoaded()
}.launch()
}
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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()) {
}
.logResourceStatus("load school announcement")
.onResourceData {
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())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceSuccess {
analytics.logEvent(
"load_school_announcement",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading School announcement result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch("load_data")
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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,30 +69,27 @@ 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 -> {
}
.logResourceStatus("load student info $infoType")
.onResourceData {
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)) {
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.data)
showCorrectData(it)
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 {
@ -105,18 +99,20 @@ class StudentInfoPresenter @Inject constructor(
}
}
}
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 {
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showCorrectData(studentInfo: StudentInfo) {

View File

@ -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()) {
}
.logResourceStatus("load timetable data")
.onResourceData {
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())
showContent(it.lessons.isNotEmpty())
showEmpty(it.lessons.isEmpty())
updateData(it.lessons)
setDayHeaderMessage(it.headers.singleOrNull { header -> header.date == currentDate }?.content)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "timetable",
"items" to it.data!!.lessons.size
"items" to it.lessons.size
)
}
Status.ERROR -> {
Timber.i("Loading timetable result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun updateData(lessons: List<Timetable>) {

View File

@ -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())
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())
}
}
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "additional_lessons",
"items" to it.data!!.additional.size
"items" to it.additional.size
)
}
Status.ERROR -> {
Timber.i("Loading additional lessons result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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()) {
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)
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())
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "completed_lessons",
"items" to it.data!!.size
"items" to it.size
)
}
Status.ERROR -> {
Timber.i("Loading completed lessons result: An exception occurred")
completedLessonsErrorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}.launch()
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -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()
}

View File

@ -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) {

View File

@ -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 <ResultType, RequestType> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline query: () -> Flow<ResultType>,
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 <ResultType, RequestType, T> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline query: () -> Flow<ResultType>,
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 <T> flowWithResource(block: suspend () -> T) = flow {
emit(Resource.loading())
emit(Resource.success(block()))
}.catch { emit(Resource.error(it)) }
@OptIn(FlowPreview::class)
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.loading())
emitAll(block().filter { it.status != Status.LOADING || (it.status == Status.LOADING && it.data != null) })
}.catch { emit(Resource.error(it)) }
fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {
if (it.status != Status.LOADING) callback()
}
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it.status != Status.LOADING }.first()
suspend fun <T> Flow<Resource<T>>.waitForResult() =
takeWhile { it.status == Status.LOADING }.collect()

View File

@ -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() },

View File

@ -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() }) }

View File

@ -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() }) }

View File

@ -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() }) }

View File

@ -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) =

View File

@ -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)

View File

@ -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 {

View File

@ -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<Message>()) }) }
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)) }
}

View File

@ -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() }) }

View File

@ -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() }) }

View File

@ -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 <T> Flow<Resource<T>>.getResult() = toList()[1].data!!
private suspend fun <T> Flow<Resource<T>>.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<Grade>() 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<Grade>() 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<Grade>() to emptyList() }
coEvery { gradeRepository.getGrades(student, semesters[2], true) } returns flowWithResource { emptyList<Grade>() to emptyList() }
coEvery {
gradeRepository.getGrades(
student,
semesters[1],
true
)
} returns resourceFlow { emptyList<Grade>() to emptyList() }
coEvery {
gradeRepository.getGrades(
student,
semesters[2],
true
)
} returns resourceFlow { emptyList<Grade>() 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(
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(
)
)
)
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(
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(
)
)
)
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(
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(
)
)
)
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<Grade>() to firstSummaries }
resourceFlow { listOf<Grade>() to firstSummaries }
val items = runBlocking {
gradeAverageProvider.getGradesDetailsWithAverage(

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.utils
import io.github.wulkanowy.data.Resource
enum class Status {
LOADING, SUCCESS, ERROR
}
val <T> Resource<T>.status
get() = when (this) {
is Resource.Error -> Status.ERROR
is Resource.Loading -> Status.LOADING
is Resource.Success -> Status.SUCCESS
}

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More