Merge branch 'release/1.6.0'

This commit is contained in:
Mikołaj Pich 2022-04-02 22:01:53 +02:00
commit b371fd6709
175 changed files with 6316 additions and 3264 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=4
[*.json]
indent_size=2
[*.{kt,kts}]
disabled_rules=import-ordering,no-wildcard-imports

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 Wulkanowy
Copyright 2022 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -22,8 +22,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 31
versionCode 103
versionName "1.5.0"
versionCode 104
versionName "1.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy"
@ -73,6 +73,8 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
}
debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
@ -169,14 +171,14 @@ huaweiPublish {
ext {
work_manager = "2.7.1"
android_hilt = "1.0.0"
room = "2.4.0"
room = "2.4.2"
chucker = "3.5.2"
mockk = "1.12.1"
mockk = "1.12.2"
coroutines = "1.6.0"
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.5.0"
implementation "io.github.wulkanowy:sdk:1.6.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@ -184,19 +186,19 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.7.0"
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.4.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.5.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.2.0'
@ -204,7 +206,7 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
@ -228,24 +230,25 @@ dependencies {
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:1.4.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
playImplementation platform('com.google.firebase:firebase-bom:29.0.3')
playImplementation platform('com.google.firebase:firebase-bom:29.3.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.android.play:core:1.10.2'
playImplementation 'com.google.android.play:core:1.10.3'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
playImplementation 'com.google.android.gms:play-services-ads:20.6.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.200'
hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04'
testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk"

File diff suppressed because it is too large Load Diff

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
fun <T> error(error: Throwable?, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, error)
}
sealed class Resource<T> {
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
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
}
val <T> Resource<T>.errorOrNull: Throwable?
get() = when (this) {
is Resource.Error -> this.error
else -> 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

@ -1,72 +1,10 @@
package io.github.wulkanowy.data.db
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.*
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.NotificationDao
import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentInfo
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.migrations.*
import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton
@ -108,6 +46,7 @@ import javax.inject.Singleton
autoMigrations = [
AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -116,7 +55,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 47
const val VERSION_SCHEMA = 48
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),

View File

@ -1,11 +1,14 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.*
import java.util.*
import java.time.Instant
import java.time.LocalDate
import java.time.Month
@ -58,4 +61,11 @@ class Converters {
emptyList() // handle errors from old gson Pair serialized data
}
}
@TypeConverter
fun destinationToString(destination: Destination) = json.encodeToString(destination)
@TypeConverter
fun stringToDestination(destination: String): Destination = json.decodeFromString(destination)
}

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

@ -24,5 +24,8 @@ data class GradeSemesterStatistics(
var id: Long = 0
@Transient
var average: String = ""
var classAverage: String = ""
@Transient
var studentAverage: String = ""
}

View File

@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
import java.time.Instant
@Entity(tableName = "Notifications")
@ -18,6 +19,9 @@ data class Notification(
val type: NotificationType,
@ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}")
val destination: Destination,
val date: Instant,
val data: String? = null

View File

@ -1,10 +1,10 @@
package io.github.wulkanowy.data.pojos
import android.content.Intent
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
data class NotificationData(
val intentToStart: Intent,
val destination: Destination,
val title: String,
val content: String
)
@ -13,7 +13,7 @@ data class GroupNotificationData(
val notificationDataList: List<NotificationData>,
val title: String,
val content: String,
val intentToStart: Intent,
val destination: Destination,
val type: NotificationType
)

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)
@ -63,20 +68,16 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items ->
when (subjectName) {
"Wszystkie" -> {
val numerator = items.map {
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
val summaryItem = GradePartialStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
classAverage = if (numerator.isEmpty()) "" else numerator.average().let {
"%.2f".format(Locale.FRANCE, it)
},
studentAverage = "",
classAverage = items.map { it.classAverage }.getSummaryAverage(),
studentAverage = items.map { it.studentAverage }.getSummaryAverage(),
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)).reversed()
)
listOf(summaryItem) + items
}
else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems()
@ -90,6 +91,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean,
) = networkBoundResource(
mutex = semesterMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(semesterCacheKey, semester)
@ -112,29 +114,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item ->
item.copy().apply {
val denominator = item.amounts.sum()
average = if (denominator == 0) "" else {
classAverage = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount
}.sum().toDouble() / denominator).let {
"%.2f".format(Locale.FRANCE, it)
}
}.sum().toDouble() / denominator).asAverageString()
}
}
}
when (subjectName) {
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
studentGrade = 0
).apply {
average = itemsWithAverage.mapNotNull {
it.average.replace(",", ".").toDoubleOrNull()
}.average().let {
"%.2f".format(Locale.FRANCE, it)
"Wszystkie" -> {
val summaryItem = GradeSemesterStatistics(
studentId = semester.studentId,
semesterId = semester.semesterId,
subject = subjectName,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
studentGrade = 0,
).apply {
classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage()
studentAverage = items
.mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } }
.average().asAverageString()
}
}).reversed()
listOf(summaryItem) + itemsWithAverage
}
else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems()
}
@ -147,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
@ -171,6 +174,19 @@ class GradeStatisticsRepository @Inject constructor(
}
)
private fun List<String>.getSummaryAverage(): String {
val averages = mapNotNull {
it.replace(",", ".").toDoubleOrNull()
}
return averages.average()
.asAverageString()
.takeIf { averages.isNotEmpty() }
.orEmpty()
}
private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this)
private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach {

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?.message?.content.isNullOrBlank() },
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
@ -153,20 +151,27 @@ class MessageRepository @Inject constructor(
recipients = recipients.mapFromEntities()
)
suspend fun deleteMessage(student: Student, message: Message) {
val isDeleted = sdk.init(student).deleteMessages(
messages = listOf(message.messageId), message.folderId
)
suspend fun deleteMessages(student: Student, messages: List<Message>) {
val folderId = messages.first().folderId
val isDeleted = sdk.init(student)
.deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
if (message.folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply {
id = message.id
content = message.content
if (folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessages = messages.map {
it.copy(folderId = MessageFolder.TRASHED.id)
.apply {
id = it.id
content = it.content
}
}
messagesDb.updateAll(listOf(deletedMessage))
} else messagesDb.deleteAll(listOf(message))
messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages)
}
suspend fun deleteMessage(student: Student, message: Message) =
deleteMessages(student, listOf(message))
var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { json.decodeFromString(it) }

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

@ -0,0 +1,32 @@
package io.github.wulkanowy.data.serializers
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.nullable
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.LocalDate
@OptIn(ExperimentalSerializationApi::class)
object LocalDateSerializer : KSerializer<LocalDate?> {
override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG).nullable
override fun serialize(encoder: Encoder, value: LocalDate?) {
if (value == null) {
encoder.encodeNull()
} else {
encoder.encodeNotNullMark()
encoder.encodeLong(value.toEpochDay())
}
}
override fun deserialize(decoder: Decoder): LocalDate? =
if (decoder.decodeNotNullMark()) {
LocalDate.ofEpochDay(decoder.decodeLong())
} else {
decoder.decodeNull()
}
}

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

@ -15,6 +15,9 @@ import javax.inject.Singleton
@Singleton
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
// Destination cannot be used here as shortcuts
// require their intents to only use primitive types (see PersistableBundle.isValidType).
private val destinations = mapOf(
"grade" to Destination.Grade,
"attendance" to Destination.Attendance,

View File

@ -14,6 +14,7 @@ import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.data.repositories.NotificationRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.getCompatBitmap
import io.github.wulkanowy.utils.getCompatColor
@ -47,7 +48,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity(
context,
Random.nextInt(),
notificationData.intentToStart,
SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
)
@ -92,7 +93,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity(
context,
Random.nextInt(),
notificationData.intentToStart,
SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
)
@ -146,7 +147,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity(
context,
Random.nextInt(),
groupNotificationData.intentToStart,
SplashActivity.getStartIntent(context, groupNotificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
)
@ -168,6 +169,7 @@ class AppNotificationManager @Inject constructor(
studentId = student.id,
title = notificationData.title,
content = notificationData.content,
destination = notificationData.destination,
type = notificationType,
date = Instant.now(),
)

View File

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString
import java.time.Instant
@ -23,8 +22,9 @@ class ChangeTimetableNotification @Inject constructor(
suspend fun notify(items: List<Timetable>, student: Student) {
val currentTime = Instant.now()
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
val notificationDataList = changedLessons.groupBy { it.date }
.map { (date, lessons) ->
val lessonsByDate = changedLessons.groupBy { it.date }
val notificationDataList = lessonsByDate
.flatMap { (date, lessons) ->
getNotificationContents(date, lessons).map {
NotificationData(
title = context.getPlural(
@ -32,14 +32,10 @@ class ChangeTimetableNotification @Inject constructor(
1
),
content = it,
intentToStart = SplashActivity.getStartIntent(
context = context,
destination = Destination.Timetable(date)
)
destination = Destination.Timetable(date)
)
}
}
.flatten()
.ifEmpty { return }
val groupNotificationData = GroupNotificationData(
@ -53,7 +49,7 @@ class ChangeTimetableNotification @Inject constructor(
changedLessons.size,
changedLessons.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()),
destination = Destination.Timetable(lessonsByDate.toSortedMap().firstKey()),
type = NotificationType.CHANGE_TIMETABLE
)

View File

@ -31,7 +31,7 @@ class NewAttendanceNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1),
content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance)
destination = Destination.Attendance
)
}
@ -46,7 +46,7 @@ class NewAttendanceNotification @Inject constructor(
notificationDataList.size,
notificationDataList.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance),
destination = Destination.Attendance,
type = NotificationType.NEW_ATTENDANCE
)

View File

@ -31,7 +31,7 @@ class NewConferenceNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
destination = Destination.Conference
)
}
@ -43,7 +43,7 @@ class NewConferenceNotification @Inject constructor(
lines.size,
lines.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference),
destination = Destination.Conference,
type = NotificationType.NEW_CONFERENCE
)

View File

@ -31,7 +31,7 @@ class NewExamNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
destination = Destination.Exam,
)
}
@ -43,7 +43,7 @@ class NewExamNotification @Inject constructor(
lines.size,
lines.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
destination = Destination.Exam,
type = NotificationType.NEW_EXAM
)

View File

@ -26,7 +26,7 @@ class NewGradeNotification @Inject constructor(
append("${it.subject}: ${it.entry}")
if (it.comment.isNotBlank()) append(" (${it.comment})")
},
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
)
}
@ -34,7 +34,7 @@ class NewGradeNotification @Inject constructor(
notificationDataList = notificationDataList,
title = context.getPlural(R.plurals.grade_new_items, items.size),
content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
type = NotificationType.NEW_GRADE_DETAILS
)
@ -46,7 +46,7 @@ class NewGradeNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
content = "${it.subject}: ${it.predictedGrade}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
)
}
@ -58,7 +58,7 @@ class NewGradeNotification @Inject constructor(
items.size,
items.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
type = NotificationType.NEW_GRADE_PREDICTED
)
@ -70,7 +70,7 @@ class NewGradeNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.grade_new_items_final, 1),
content = "${it.subject}: ${it.finalGrade}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
)
}
@ -82,7 +82,7 @@ class NewGradeNotification @Inject constructor(
items.size,
items.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
destination = Destination.Grade,
type = NotificationType.NEW_GRADE_FINAL
)

View File

@ -31,7 +31,7 @@ class NewHomeworkNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.homework_notify_new_item_title, 1),
content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
destination = Destination.Homework,
)
}
@ -42,7 +42,7 @@ class NewHomeworkNotification @Inject constructor(
lines.size,
lines.size
),
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
destination = Destination.Homework,
type = NotificationType.NEW_HOMEWORK,
notificationDataList = notificationDataList
)

View File

@ -22,7 +22,7 @@ class NewLuckyNumberNotification @Inject constructor(
R.string.lucky_number_notify_new_item,
item.luckyNumber.toString()
),
intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber)
destination = Destination.LuckyNumber
)
appNotificationManager.sendSingleNotification(

View File

@ -22,7 +22,7 @@ class NewMessageNotification @Inject constructor(
NotificationData(
title = context.getPlural(R.plurals.message_new_items, 1),
content = "${it.sender}: ${it.subject}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
destination = Destination.Message,
)
}
@ -30,7 +30,7 @@ class NewMessageNotification @Inject constructor(
notificationDataList = notificationDataList,
title = context.getPlural(R.plurals.message_new_items, items.size),
content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size),
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
destination = Destination.Message,
type = NotificationType.NEW_MESSAGE
)

View File

@ -29,13 +29,13 @@ class NewNoteNotification @Inject constructor(
NotificationData(
title = context.getPlural(titleRes, 1),
content = "${it.teacher}: ${it.category}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
destination = Destination.Note,
)
}
val groupNotificationData = GroupNotificationData(
notificationDataList = notificationDataList,
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
destination = Destination.Note,
title = context.getPlural(R.plurals.note_new_items, items.size),
content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size),
type = NotificationType.NEW_NOTE

View File

@ -21,10 +21,7 @@ class NewSchoolAnnouncementNotification @Inject constructor(
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
val notificationDataList = items.map {
NotificationData(
intentToStart = SplashActivity.getStartIntent(
context = context,
destination = Destination.SchoolAnnouncement
),
destination = Destination.SchoolAnnouncement,
title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title,
1
@ -34,10 +31,7 @@ class NewSchoolAnnouncementNotification @Inject constructor(
}
val groupNotificationData = GroupNotificationData(
type = NotificationType.NEW_ANNOUNCEMENT,
intentToStart = SplashActivity.getStartIntent(
context = context,
destination = Destination.SchoolAnnouncement
),
destination = Destination.SchoolAnnouncement,
title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title,
items.size

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,28 +30,28 @@ open class BasePresenter<T : BaseView>(
}
fun onExpiredLoginSelected() {
flowWithResource {
val student = studentRepository.getCurrentStudent(false)
studentRepository.logoutStudent(student)
Timber.i("Attempt to switch the student after the session expires")
val students = studentRepository.getSavedStudents(false)
if (students.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(students[0])
presenterScope.launch {
runCatching {
val student = studentRepository.getCurrentStudent(false)
studentRepository.logoutStudent(student)
val students = studentRepository.getSavedStudents(false)
if (students.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(students[0])
}
}
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Attempt to switch the student after the session expires")
Status.SUCCESS -> {
.onFailure {
Timber.i("Switch student result: An exception occurred")
errorHandler.dispatch(it)
}
.onSuccess {
Timber.i("Switch student result: Open login view")
view?.openClearLoginView()
}
Status.ERROR -> {
Timber.i("Switch student result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("expired")
}
}
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules
import androidx.fragment.app.Fragment
import io.github.wulkanowy.data.serializers.LocalDateSerializer
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
@ -14,18 +15,19 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import java.io.Serializable
import kotlinx.serialization.Serializable
import java.time.LocalDate
sealed interface Destination : Serializable {
@Serializable
sealed class Destination private constructor() : java.io.Serializable {
/*
Type in children classes have to be as getter to avoid null in enums
https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
*/
val type: Type
abstract val type: Type
val fragment: Fragment
abstract val fragment: Fragment
enum class Type(val defaultDestination: Destination) {
DASHBOARD(Dashboard),
@ -43,94 +45,84 @@ sealed interface Destination : Serializable {
MESSAGE(Message);
}
object Dashboard : Destination {
@Serializable
object Dashboard : Destination() {
override val type get() = Type.DASHBOARD
override val fragment get() = DashboardFragment.newInstance()
}
object Grade : Destination {
@Serializable
object Grade : Destination() {
override val type get() = Type.GRADE
override val fragment get() = GradeFragment.newInstance()
}
object Attendance : Destination {
@Serializable
object Attendance : Destination() {
override val type get() = Type.ATTENDANCE
override val fragment get() = AttendanceFragment.newInstance()
}
object Exam : Destination {
@Serializable
object Exam : Destination() {
override val type get() = Type.EXAM
override val fragment get() = ExamFragment.newInstance()
}
data class Timetable(val date: LocalDate? = null) : Destination {
@Serializable
data class Timetable(
@Serializable(with = LocalDateSerializer::class)
private val date: LocalDate? = null
) : Destination() {
override val type get() = Type.TIMETABLE
override val fragment get() = TimetableFragment.newInstance(date)
}
object Homework : Destination {
@Serializable
object Homework : Destination() {
override val type get() = Type.HOMEWORK
override val fragment get() = HomeworkFragment.newInstance()
}
object Note : Destination {
@Serializable
object Note : Destination() {
override val type get() = Type.NOTE
override val fragment get() = NoteFragment.newInstance()
}
object Conference : Destination {
@Serializable
object Conference : Destination() {
override val type get() = Type.CONFERENCE
override val fragment get() = ConferenceFragment.newInstance()
}
object SchoolAnnouncement : Destination {
@Serializable
object SchoolAnnouncement : Destination() {
override val type get() = Type.SCHOOL_ANNOUNCEMENT
override val fragment get() = SchoolAnnouncementFragment.newInstance()
}
object School : Destination {
@Serializable
object School : Destination() {
override val type get() = Type.SCHOOL
override val fragment get() = SchoolFragment.newInstance()
}
object LuckyNumber : Destination {
@Serializable
object LuckyNumber : Destination() {
override val type get() = Type.LUCKY_NUMBER
override val fragment get() = LuckyNumberFragment.newInstance()
}
object More : Destination {
@Serializable
object More : Destination() {
override val type get() = Type.MORE
override val fragment get() = MoreFragment.newInstance()
}
object Message : Destination {
@Serializable
object Message : Destination() {
override val type get() = Type.MESSAGE
override val fragment get() = MessageFragment.newInstance()
}
}
}

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 {
withContext(dispatchers.io) {
view?.appLibraries.orEmpty()
presenterScope.launch {
runCatching {
withContext(dispatchers.io) {
view?.appLibraries.orEmpty()
}
}
}.onEach {
when (it.status) {
Status.LOADING -> Timber.d("License data load started")
Status.SUCCESS -> view?.updateData(it.data!!)
Status.ERROR -> errorHandler.dispatch(it.error!!)
}
}.afterLoading {
.onFailure { errorHandler.dispatch(it) }
.onSuccess { view?.updateData(it) }
view?.showProgress(false)
}.launch()
}
}
}

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,40 +46,25 @@ class AccountDetailsPresenter @Inject constructor(
}
private fun loadData() {
flowWithResource { studentRepository.getSavedStudents() }
.map { studentWithSemesters ->
Resource(
data = studentWithSemesters.data?.single { it.student.id == studentId },
status = studentWithSemesters.status,
error = studentWithSemesters.error
)
}
.onEach {
when (it.status) {
Status.LOADING -> {
view?.run {
showProgress(true)
showContent(false)
}
Timber.i("Loading account details view started")
}
Status.SUCCESS -> {
Timber.i("Loading account details view result: Success")
studentWithSemesters = it.data
view?.run {
showAccountData(studentWithSemesters!!.student)
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
showContent(true)
showErrorView(false)
}
}
Status.ERROR -> {
Timber.i("Loading account details view result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
resourceFlow { studentRepository.getSavedStudentById(studentId ?: -1) }
.logResourceStatus("loading account details view")
.onResourceLoading {
view?.run {
showProgress(true)
showContent(false)
}
}
.afterLoading { view?.showProgress(false) }
.onResourceSuccess {
studentWithSemesters = it
view?.run {
showAccountData(studentWithSemesters!!.student)
enableSelectStudentButton(!studentWithSemesters!!.student.isCurrent)
showContent(true)
showErrorView(false)
}
}
.onResourceNotLoading { view?.showProgress(false) }
.onResourceError(errorHandler::dispatch)
.launch()
}
@ -105,22 +85,12 @@ class AccountDetailsPresenter @Inject constructor(
Timber.i("Select student ${studentWithSemesters!!.student.id}")
flowWithResource { studentRepository.switchStudent(studentWithSemesters!!) }
.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Attempt to change a student")
Status.SUCCESS -> {
Timber.i("Change a student result: Success")
view?.recreateMainView()
}
Status.ERROR -> {
Timber.i("Change a student result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
view?.popViewToMain()
}.launch("switch")
resourceFlow { studentRepository.switchStudent(studentWithSemesters!!) }
.logResourceStatus("change student")
.onResourceSuccess { view?.recreateMainView() }
.onResourceNotLoading { view?.popViewToMain() }
.onResourceError(errorHandler::dispatch)
.launch("switch")
}
fun onRemoveSelected() {
@ -131,7 +101,7 @@ class AccountDetailsPresenter @Inject constructor(
fun onLogoutConfirm() {
if (studentWithSemesters == null) return
flowWithResource {
resourceFlow {
val studentToLogout = studentWithSemesters!!.student
studentRepository.logoutStudent(studentToLogout)
@ -141,13 +111,13 @@ class AccountDetailsPresenter @Inject constructor(
studentRepository.switchStudent(students[0])
}
return@flowWithResource students
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Attempt to logout user")
Status.SUCCESS -> view?.run {
students
}
.logResourceStatus("logout user")
.onResourceSuccess {
view?.run {
when {
it.data!!.isEmpty() -> {
it.isEmpty() -> {
Timber.i("Logout result: Open login view")
syncManager.stopSyncWorker()
openClearLoginView()
@ -162,18 +132,16 @@ class AccountDetailsPresenter @Inject constructor(
}
}
}
Status.ERROR -> {
Timber.i("Logout result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.onResourceNotLoading {
if (studentWithSemesters?.student?.isCurrent == true) {
view?.popViewToMain()
} else {
view?.popViewToAccounts()
}
}
}.afterLoading {
if (studentWithSemesters?.student?.isCurrent == true) {
view?.popViewToMain()
} else {
view?.popViewToAccounts()
}
}.launch("logout")
.onResourceError(errorHandler::dispatch)
.launch("logout")
}
private fun showErrorViewOnError(message: String, error: Throwable) {

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

@ -2,14 +2,8 @@ package io.github.wulkanowy.ui.modules.attendance
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.*
import android.view.View.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
@ -68,7 +62,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val inflater = mode.menuInflater
inflater.inflate(R.menu.context_menu_excuse, menu)
inflater.inflate(R.menu.context_menu_attendance, menu)
return true
}

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 }
}
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showErrorView(false)
showEmpty(filteredAttendance.isEmpty())
showContent(filteredAttendance.isNotEmpty())
updateData(filteredAttendance.sortedBy { item -> item.number })
}
}
}
Status.SUCCESS -> {
Timber.i("Loading attendance result: Success")
val filteredAttendance = if (prefRepository.isShowPresent) {
it.data.orEmpty()
} else {
it.data?.filter { item -> !item.presence }.orEmpty()
}
isVulcanExcusedFunctionEnabled =
filteredAttendance.any { item -> item.excusable }
view?.apply {
updateData(filteredAttendance.sortedBy { item -> item.number })
showEmpty(filteredAttendance.isEmpty())
showErrorView(false)
showContent(filteredAttendance.isNotEmpty())
val anyExcusables = filteredAttendance.any { it.isExcusableOrNotExcused }
showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
}
analytics.logEvent(
"load_data",
"type" to "attendance",
"items" to it.data!!.size
)
}
Status.ERROR -> {
Timber.i("Loading attendance result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load attendance")
.onResourceLoading {
view?.showExcuseButton(false)
}
.mapResourceData {
if (prefRepository.isShowPresent) {
it
} else {
it.filter { item -> !item.presence }
}.sortedBy { item -> item.number }
}
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
val anyExcusables = it.any { it.isExcusableOrNotExcused }
view?.showExcuseButton(anyExcusables && (isParent || isVulcanExcusedFunctionEnabled))
analytics.logEvent(
"load_data",
"type" to "attendance",
"items" to it.size
)
}
}.launch()
.onResourceNotLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun excuseAbsence(reason: String?, toExcuseList: List<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()) {
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showContent(true)
showErrorView(false)
updateDataSet(sortItems(it.data))
}
}
}
Status.SUCCESS -> {
Timber.i("Loading attendance summary result: Success")
view?.apply {
showErrorView(false)
showEmpty(it.data!!.isEmpty())
showContent(it.data.isNotEmpty())
updateDataSet(sortItems(it.data))
}
analytics.logEvent(
"load_data",
"type" to "attendance_summary",
"items" to it.data!!.size,
"item_id" to subjectId
)
}
Status.ERROR -> {
Timber.i("Loading attendance summary result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load attendance summary")
.mapResourceData(this::sortItems)
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateDataSet(it)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "attendance_summary",
"items" to it.size,
"item_id" to subjectId
)
}
}.launch()
.onResourceNotLoading {
view?.run {
showProgress(false)
showRefresh(false)
enableSwipe(true)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun sortItems(items: List<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")
view?.run {
view?.updateSubjects(ArrayList(it.data.map { subject -> subject.name }))
showSubjects(true)
}
}
Status.ERROR -> {
Timber.i("Loading attendance summary subjects result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load attendance summary subjects")
.onResourceData {
subjects = it
view?.run {
view?.updateSubjects(it.map { subject -> subject.name }.toList())
showSubjects(true)
}
}
}.launch("subjects")
.onResourceError(errorHandler::dispatch)
.launch("subjects")
}
}

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

View File

@ -204,4 +204,4 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
presenter.onDetachView()
super.onDestroyView()
}
}
}

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,16 +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,
@ -295,72 +289,69 @@ class DashboardPresenter @Inject constructor(
)
errorHandler.dispatch(it)
}
.launch("horizontal_group")
.launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}")
}
private fun loadGrades(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
gradeRepository.getGrades(student, semester, forceRefresh)
}.map { originalResource ->
val filteredSubjectWithGrades = originalResource.data?.first
.orEmpty()
.filter { it.date >= LocalDate.now().minusDays(7) }
.groupBy { it.subject }
.mapValues { entry ->
entry.value
.take(5)
.sortedByDescending { it.date }
}
.toList()
.sortedByDescending { (_, grades) -> grades[0].date }
.toMap()
}
.mapResourceData { (details, _) ->
val filteredSubjectWithGrades = details
.filter { it.date >= LocalDate.now().minusDays(7) }
.groupBy { it.subject }
.mapValues { entry ->
entry.value
.take(5)
.sortedByDescending { it.date }
}
.toList()
.sortedByDescending { (_, grades) -> grades[0].date }
.toMap()
Resource(
status = originalResource.status,
data = filteredSubjectWithGrades.takeIf { originalResource.data != null },
error = originalResource.error
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard grades data started")
if (forceRefresh) return@onEach
filteredSubjectWithGrades
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard grades data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Grades(
subjectWithGrades = it.dataOrNull,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
), forceRefresh
)
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
), forceRefresh
)
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.GRADES
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.GRADES
}
}
is Resource.Success -> {
Timber.i("Loading dashboard grades result: Success")
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme
),
forceRefresh
)
}
is Resource.Error -> {
Timber.i("Loading dashboard grades result: An exception occurred")
errorHandler.dispatch(it.error)
updateData(DashboardItem.Grades(error = it.error), forceRefresh)
}
}
Status.SUCCESS -> {
Timber.i("Loading dashboard grades result: Success")
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme
),
forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard grades result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Grades(error = it.error), forceRefresh)
}
}
}.launch("dashboard_grades")
.launchWithUniqueRefreshJob("dashboard_grades", forceRefresh)
}
private fun loadLessons(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay
@ -371,40 +362,41 @@ class DashboardPresenter @Inject constructor(
end = date.plusDays(1),
forceRefresh = forceRefresh
)
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard lessons data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Lessons(it.dataOrNull, isLoading = true),
forceRefresh
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard lessons data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Lessons(it.data, isLoading = true),
forceRefresh
)
if (!it.data?.lessons.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.LESSONS
if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.LESSONS
}
}
is Resource.Success -> {
Timber.i("Loading dashboard lessons result: Success")
updateData(
DashboardItem.Lessons(it.data), forceRefresh
)
}
is Resource.Error -> {
Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error)
updateData(
DashboardItem.Lessons(error = it.error), forceRefresh
)
}
}
Status.SUCCESS -> {
Timber.i("Loading dashboard lessons result: Success")
updateData(
DashboardItem.Lessons(it.data), forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(
DashboardItem.Lessons(error = it.error), forceRefresh
)
}
}
}.launch("dashboard_lessons")
.launchWithUniqueRefreshJob("dashboard_lessons", forceRefresh)
}
private fun loadHomework(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay
@ -415,73 +407,79 @@ class DashboardPresenter @Inject constructor(
end = date,
forceRefresh = forceRefresh
)
}.map { homeworkResource ->
val currentDate = LocalDate.now()
}
.mapResourceData { homework ->
val currentDate = LocalDate.now()
val filteredHomework = homeworkResource.data
?.filter { (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone }
?.sortedBy { it.date }
val filteredHomework = homework.filter {
(it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone
}.sortedBy { it.date }
homeworkResource.copy(data = filteredHomework)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard homework data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true),
forceRefresh
)
filteredHomework
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard homework data started")
if (forceRefresh) return@onEach
val data = it.dataOrNull.orEmpty()
updateData(
DashboardItem.Homework(data, isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.HOMEWORK
if (data.isNotEmpty()) {
firstLoadedItemList += DashboardItem.Type.HOMEWORK
}
}
is Resource.Success -> {
Timber.i("Loading dashboard homework result: Success")
updateData(DashboardItem.Homework(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard homework result: An exception occurred")
errorHandler.dispatch(it.error)
updateData(DashboardItem.Homework(error = it.error), forceRefresh)
}
}
Status.SUCCESS -> {
Timber.i("Loading dashboard homework result: Success")
updateData(DashboardItem.Homework(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard homework result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Homework(error = it.error), forceRefresh)
}
}
}.launch("dashboard_homework")
.launchWithUniqueRefreshJob("dashboard_homework", forceRefresh)
}
private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard announcements data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true),
forceRefresh
)
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard announcements data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
}
}
is Resource.Success -> {
Timber.i("Loading dashboard announcements result: Success")
updateData(DashboardItem.Announcements(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard announcements result: An exception occurred")
errorHandler.dispatch(it.error)
updateData(DashboardItem.Announcements(error = it.error), forceRefresh)
}
}
Status.SUCCESS -> {
Timber.i("Loading dashboard announcements result: Success")
updateData(DashboardItem.Announcements(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard announcements result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Announcements(error = it.error), forceRefresh)
}
}
}.launch("dashboard_announcements")
.launchWithUniqueRefreshJob("dashboard_announcements", forceRefresh)
}
private fun loadExams(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams(
@ -492,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)
}
}
}.launch("dashboard_exams")
}
.launchWithUniqueRefreshJob("dashboard_exams", forceRefresh)
}
private fun loadConferences(student: Student, forceRefresh: Boolean) {
flowWithResourceIn {
flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences(
@ -534,59 +529,62 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh,
startDate = Instant.now(),
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard conferences data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
forceRefresh
)
}
.onEach {
when (it) {
is Resource.Loading -> {
Timber.i("Loading dashboard conferences data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.CONFERENCES
if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.CONFERENCES
}
}
is Resource.Success -> {
Timber.i("Loading dashboard conferences result: Success")
updateData(DashboardItem.Conferences(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard conferences result: An exception occurred")
errorHandler.dispatch(it.error)
updateData(DashboardItem.Conferences(error = it.error), forceRefresh)
}
}
Status.SUCCESS -> {
Timber.i("Loading dashboard conferences result: Success")
updateData(DashboardItem.Conferences(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard conferences result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Conferences(error = it.error), forceRefresh)
}
}
}.launch("dashboard_conferences")
.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
@ -594,7 +592,7 @@ class DashboardPresenter @Inject constructor(
}
}
}
.launch("dashboard_admin_messages")
.launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh)
}
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
@ -733,4 +731,18 @@ class DashboardPresenter @Inject constructor(
dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal
}
}
}
private fun Flow<Resource<*>>.launchWithUniqueRefreshJob(name: String, forceRefresh: Boolean) {
val jobName = if (forceRefresh) "$name-forceRefresh" else name
if (forceRefresh) {
onEach {
if (it is Resource.Success) {
cancelJobs(jobName)
}
}.launch(jobName)
} else {
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 }}")
view?.shareLogs(it.data)
}
Status.ERROR -> {
Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it.error!!)
resourceFlow { loggerRepository.getLogFiles() }
.onEach {
when (it) {
is Resource.Loading -> Timber.d("Loading logs files started")
is Resource.Success -> {
Timber.i("Loading logs files result: ${it.data.joinToString { file -> file.name }}")
view?.shareLogs(it.data)
}
is Resource.Error -> {
Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it.error)
}
}
}
}.launch("share")
.launch("share")
return true
}
@ -44,18 +46,20 @@ class LogViewerPresenter @Inject constructor(
}
private fun loadLogFile() {
flowWithResource { loggerRepository.getLastLogLines() }.onEach {
when (it.status) {
Status.LOADING -> Timber.d("Loading last log file started")
Status.SUCCESS -> {
Timber.i("Loading last log file result: load ${it.data!!.size} lines")
view?.setLines(it.data)
}
Status.ERROR -> {
Timber.i("Loading last log file result: An exception occurred")
errorHandler.dispatch(it.error!!)
resourceFlow { loggerRepository.getLastLogLines() }
.onEach {
when (it) {
is Resource.Loading -> Timber.d("Loading last log file started")
is Resource.Success -> {
Timber.i("Loading last log file result: load ${it.data.size} lines")
view?.setLines(it.data)
}
is Resource.Error -> {
Timber.i("Loading last log file result: An exception occurred")
errorHandler.dispatch(it.error)
}
}
}
}.launch("file")
.launch("file")
}
}

View File

@ -25,8 +25,8 @@ private fun generateTimetable(subject: String, room: String, roomOld: String) =
diaryId = 0,
date = LocalDate.now().minusDays(Random.nextLong(0, 8)),
number = 1,
start = Instant.now(),
end = Instant.now().plus(Duration.ofHours(1)),
start = Instant.now().plus(Duration.ofHours(1)),
end = Instant.now(),
subjectOld = "",
group = "",
room = room,

View File

@ -5,10 +5,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.DialogExamBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openCalendarEventAdd
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime
class ExamDialog : DialogFragment() {
@ -46,10 +49,21 @@ class ExamDialog : DialogFragment() {
examDialogSubjectValue.text = exam.subject
examDialogTypeValue.text = exam.type
examDialogTeacherValue.text = exam.teacher
examDialogDateValue.text = exam.entryDate.toFormattedString()
examDialogDescriptionValue.text = exam.description
examDialogEntryDateValue.text = exam.entryDate.toFormattedString()
examDialogDeadlineDateValue.text = exam.date.toFormattedString()
examDialogDescriptionValue.text = exam.description.ifBlank {
getString(R.string.all_no_data)
}
examDialogClose.setOnClickListener { dismiss() }
examDialogAddToCalendar.setOnClickListener {
requireContext().openCalendarEventAdd(
title = "${exam.subject} - ${exam.type}",
description = exam.description,
start = exam.date.atTime(LocalTime.of(8, 0)),
end = exam.date.atTime(LocalTime.of(8, 45)),
)
}
}
}
}

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 {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}.launch("holidays")
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}
.launch("holidays")
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading exam data started")
flowWithResourceIn {
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
if (!it.data.isNullOrEmpty()) {
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showContent(true)
updateData(createExamItems(it.data))
}
}
}
Status.SUCCESS -> {
Timber.i("Loading exam result: Success")
view?.apply {
updateData(createExamItems(it.data!!))
showEmpty(it.data.isEmpty())
showErrorView(false)
showContent(it.data.isNotEmpty())
}
analytics.logEvent(
"load_data",
"type" to "exam",
"items" to it.data!!.size
)
}
Status.ERROR -> {
Timber.i("Loading exam result: An exception occurred")
errorHandler.dispatch(it.error!!)
examRepository.getExams(
student = student,
semester = semester,
start = currentDate.monday,
end = currentDate.sunday,
forceRefresh = forceRefresh
)
}
.logResourceStatus("load exam data")
.mapResourceData { createExamItems(it) }
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "exam",
"items" to it.size
)
}
}.launch()
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@ -181,8 +169,10 @@ class ExamPresenter @Inject constructor(
view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM"))
updateNavigationWeek(
"${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
}
}
}

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
@ -10,17 +9,13 @@ import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.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)
@ -37,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) {
@ -83,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) {
@ -115,7 +110,7 @@ class GradeAverageProvider @Inject constructor(
}
secondSemesterSubject.copy(average = updatedAverage)
}
secondSemesterGradeSubject.copy(data = updatedData)
secondSemesterGradeSubject.mapData { updatedData!! }
}
}
@ -144,20 +139,20 @@ class GradeAverageProvider @Inject constructor(
isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject?
): Double {
): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
val secondSemesterAverage =
secondSemesterSubject.grades.updateModifiers(student)
.calcAverage(isOptionalArithmeticAverage)
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student)
.calcAverage(isOptionalArithmeticAverage)
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
(secondSemesterAverage + firstSemesterAverage) / divider
} else {
(secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / divider
}
(secondSemesterAverage + firstSemesterAverage) / divider
} else {
val divider = if (secondSemesterSubject.average > 0) 2 else 1
(secondSemesterSubject.average + (firstSemesterSubject?.average
?: secondSemesterSubject.average)) / divider
}
private fun getGradeSubjects(
@ -168,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,
@ -192,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()
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
schoolYear = current.schoolYear
semesters = it.data.filter { semester -> semester.diaryId == current.diaryId }
view?.setCurrentSemesterName(current.semesterName, schoolYear)
view?.run {
Timber.i("Loading grade result: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
showErrorView(false)
showSemesterSwitch(true)
}
}
Status.ERROR -> {
Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load grade data")
.onResourceData {
val current = it.getCurrentOrLast()
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
schoolYear = current.schoolYear
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
view?.setCurrentSemesterName(current.semesterName, schoolYear)
view?.run {
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
loadChild(currentPageIndex)
showErrorView(false)
showSemesterSwitch(true)
}
}
}.launch()
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -10,11 +10,8 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.DialogGradeBinding
import io.github.wulkanowy.utils.colorStringId
import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getGradeColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.*
class GradeDetailsDialog : DialogFragment() {
@ -80,9 +77,7 @@ class GradeDetailsDialog : DialogFragment() {
setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
}
gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) {
getString(R.string.all_no_data)
} else grade.teacher
gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) }
gradeDialogDescriptionValue.text = grade.run {
when {

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

@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.LegendEntry
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.formatter.ValueFormatter
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
@ -136,20 +131,50 @@ class GradeStatisticsAdapter @Inject constructor() :
binding: ItemGradeStatisticsPieBinding,
partials: GradePartialStatistics
) {
bindPieChart(binding, partials.subject, partials.classAverage, partials.classAmounts)
val studentAverage = partials.studentAverage.takeIf { it.isNotEmpty() }?.let {
binding.root.context.getString(R.string.grade_statistics_student_average, it)
}
bindPieChart(
binding = binding,
subject = partials.subject,
average = partials.classAverage,
studentValue = studentAverage,
amounts = partials.classAmounts
)
}
private fun bindSemesterChart(
binding: ItemGradeStatisticsPieBinding,
semester: GradeSemesterStatistics
) {
bindPieChart(binding, semester.subject, semester.average, semester.amounts)
val studentAverage = semester.studentAverage.takeIf { it.isNotBlank() }
val studentGrade = semester.studentGrade.takeIf { it != 0 }
val studentValue = when {
studentAverage != null -> binding.root.context.getString(
R.string.grade_statistics_student_average,
studentAverage
)
studentGrade != null -> binding.root.context.getString(
R.string.grade_statistics_student_grade,
studentGrade.toString()
)
else -> null
}
bindPieChart(
binding = binding,
subject = semester.subject,
average = semester.classAverage,
studentValue = studentValue,
amounts = semester.amounts
)
}
private fun bindPieChart(
binding: ItemGradeStatisticsPieBinding,
subject: String,
average: String,
studentValue: String?,
amounts: List<Int>
) {
with(binding.gradeStatisticsPieTitle) {
@ -208,13 +233,13 @@ class GradeStatisticsAdapter @Inject constructor() :
val numberOfGradesString = amounts.fold(0) { acc, it -> acc + it }
.let { resources.getQuantityString(R.plurals.grade_number_item, it, it) }
val averageString =
binding.root.context.getString(R.string.grade_statistics_average, average)
binding.root.context.getString(R.string.grade_statistics_class_average, average)
minAngleForSlices = 25f
description.isEnabled = false
centerText =
numberOfGradesString + ("\n\n" + averageString).takeIf { average.isNotBlank() }
.orEmpty()
.orEmpty() + studentValue?.let { "\n$it" }.orEmpty()
setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground))
setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary))

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")
view?.run {
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
updateSubjects(
data = it.data.map { subject -> subject.name },
selectedIndex = it.data.indexOfFirst { subject ->
subject.name == currentSubjectName
},
)
}
}
Status.ERROR -> {
Timber.i("Loading grade stats subjects result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load grade stats subjects")
.onResourceData {
subjects = it
view?.run {
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
updateSubjects(
data = it.map { subject -> subject.name },
selectedIndex = it.indexOfFirst { subject ->
subject.name == currentSubjectName
},
)
}
}
}.launch("subjects")
.onResourceError(errorHandler::dispatch)
.launch("subjects")
}
private fun loadDataByType(
@ -168,7 +154,7 @@ class GradeStatisticsPresenter @Inject constructor(
else -> subjectName
}
flowWithResourceIn {
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
val semesters = semesterRepository.getSemesters(student)
val semester = semesters.first { item -> item.semesterId == semesterId }
@ -201,58 +187,43 @@ class GradeStatisticsPresenter @Inject constructor(
}
}
}
}.onEach {
when (it.status) {
Status.LOADING -> {
val isNoContent = it.data == null || checkIsNoContent(it.data, type)
if (!isNoContent) {
view?.run {
showEmpty(isNoContent)
showErrorView(false)
enableSwipe(true)
showRefresh(true)
showProgress(false)
updateData(
newItems = if (isNoContent) emptyList() else it.data!!,
newTheme = preferencesRepository.gradeColorTheme,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList,
)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success")
view?.run {
val isNoContent = checkIsNoContent(it.data!!, type)
showEmpty(isNoContent)
showErrorView(false)
updateData(
newItems = if (isNoContent) emptyList() else it.data,
newTheme = preferencesRepository.gradeColorTheme,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList,
)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
}
analytics.logEvent(
"load_data",
"type" to "grade_statistics",
"items" to it.data!!.size
}
.logResourceStatus("load grade stats data")
.mapResourceData {
val isNoContent = checkIsNoContent(it, type)
if (isNoContent) emptyList() else it
}
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showEmpty(it.isEmpty())
updateData(
newItems = it,
newTheme = preferencesRepository.gradeColorTheme,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList
)
}
Status.ERROR -> {
Timber.i("Loading grade stats result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "grade_statistics",
"items" to it.size
)
}
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showRefresh(false)
showProgress(false)
notifyParentDataLoaded(semesterId)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
}
}.launch("load")
.onResourceError(errorHandler::dispatch)
.launch("load")
}
private fun checkIsNoContent(
@ -267,7 +238,8 @@ class GradeStatisticsPresenter @Inject constructor(
items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0
}
GradeStatisticsItem.DataType.POINTS -> {
items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 } ?: false
items.firstOrNull()?.points?.let { points -> points.student == .0 && points.others == .0 }
?: false
}
}
}

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")
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showEmpty(false)
showContent(true)
updateData(items)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading grade summary result: Success")
val items = createGradeSummaryItems(it.data!!)
view?.run {
showEmpty(items.isEmpty())
showContent(items.isNotEmpty())
showErrorView(false)
updateData(items)
}
analytics.logEvent(
"load_data",
"type" to "grade_summary",
"items" to it.data.size
)
}
Status.ERROR -> {
Timber.i("Loading grade summary result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.logResourceStatus("load grade summary", showData = true)
.mapResourceData { createGradeSummaryItems(it) }
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "grade_summary",
"items" to it.size
)
}
}.launch()
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showRefresh(false)
showProgress(false)
notifyParentDataLoaded(semesterId)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@ -153,9 +134,9 @@ class GradeSummaryPresenter @Inject constructor(
private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
return gradeSummary.run {
summary.finalGrade.isBlank()
&& summary.predictedGrade.isBlank()
&& average == .0
&& points.isBlank()
&& summary.predictedGrade.isBlank()
&& average == .0
&& points.isBlank()
}
}
}

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 {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}.launch("holidays")
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate
reloadNavigation()
}
.launch("holidays")
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading homework data started")
flowWithResourceIn {
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
if (!it.data.isNullOrEmpty()) {
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showContent(true)
updateData(createHomeworkItem(it.data))
}
}
}
Status.SUCCESS -> {
Timber.i("Loading homework result: Success")
view?.apply {
updateData(createHomeworkItem(it.data!!))
showEmpty(it.data.isEmpty())
showErrorView(false)
showContent(it.data.isNotEmpty())
}
analytics.logEvent(
"load_data",
"type" to "homework",
"items" to it.data!!.size
)
}
Status.ERROR -> {
Timber.i("Loading homework result: An exception occurred")
errorHandler.dispatch(it.error!!)
homeworkRepository.getHomework(
student = student,
semester = semester,
start = currentDate,
end = currentDate,
forceRefresh = forceRefresh
)
}
.logResourceStatus("loading homework")
.mapResourceData { createHomeworkItem(it) }
.onResourceData {
view?.run {
enableSwipe(true)
showProgress(false)
showErrorView(false)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
updateData(it)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
.onResourceIntermediate { view?.showRefresh(true) }
.onResourceSuccess {
analytics.logEvent(
"load_data",
"type" to "homework",
"items" to it.size
)
}
}.launch()
.onResourceNotLoading {
view?.run {
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@ -159,9 +149,10 @@ class HomeworkPresenter @Inject constructor(
private fun createHomeworkItem(items: List<Homework>): List<HomeworkItem<*>> {
return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam ->
HomeworkItem(exam, HomeworkItem.ViewType.ITEM)
}
listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed()
.map { exam ->
HomeworkItem(exam, HomeworkItem.ViewType.ITEM)
}
}.flatten()
}
@ -184,8 +175,10 @@ class HomeworkPresenter @Inject constructor(
view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM"))
updateNavigationWeek(
"${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
}
}
}

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

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")
view?.run {
showMessage(homeworkDeleteSuccess)
closeDialog()
}
}
Status.ERROR -> {
Timber.i("Homework delete result: An exception occurred")
errorHandler.dispatch(it.error!!)
resourceFlow { homeworkRepository.deleteHomework(homework) }
.logResourceStatus("homework delete")
.onResourceSuccess {
view?.run {
showMessage(homeworkDeleteSuccess)
closeDialog()
}
}
}.launch("delete")
.onResourceError(errorHandler::dispatch)
.launch("delete")
}
fun toggleDone(homework: Homework) {
flowWithResource { homeworkRepository.toggleDone(homework) }.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Homework details update start")
Status.SUCCESS -> {
Timber.i("Homework details update: Success")
view?.updateMarkAsDoneLabel(homework.isDone)
analytics.logEvent("homework_mark_as_done")
}
Status.ERROR -> {
Timber.i("Homework details update result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
resourceFlow { homeworkRepository.toggleDone(homework) }
.logResourceStatus("homework details update")
.onResourceSuccess {
view?.updateMarkAsDoneLabel(homework.isDone)
analytics.logEvent("homework_mark_as_done")
}
}.launch("toggle")
.onResourceError(errorHandler::dispatch)
.launch("toggle")
}
}

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")
hideSoftKeyboard()
showProgress(true)
showContent(false)
}
Status.SUCCESS -> {
Timber.i("Login result: Success")
analytics.logEvent(
"registration_form",
resourceFlow { getStudentsAppropriatesToLoginType() }
.logResourceStatus("login")
.onEach {
when (it) {
is Resource.Loading -> view?.run {
hideSoftKeyboard()
showProgress(true)
showContent(false)
}
is Resource.Success -> {
analytics.logEvent(
"registration_form",
"success" to true,
"students" to it.data!!.size,
"students" to it.data.size,
"error" to "No error"
)
val loginData = LoginData(
@ -154,23 +155,22 @@ class LoginAdvancedPresenter @Inject constructor(
0 -> view?.navigateToSymbol(loginData)
else -> view?.navigateToStudentSelect(it.data)
}
}
is Resource.Error -> {
analytics.logEvent(
"registration_form",
"success" to false, "students" to -1,
"error" to it.error.message.ifNullOrBlank { "No message" }
)
loginErrorHandler.dispatch(it.error)
}
}
Status.ERROR -> {
Timber.i("Login result: An exception occurred")
analytics.logEvent(
"registration_form",
"success" to false, "students" to -1,
"error" to it.error!!.message.ifNullOrBlank { "No message" }
)
loginErrorHandler.dispatch(it.error)
}.onResourceNotLoading {
view?.apply {
showProgress(false)
showContent(true)
}
}
}.afterLoading {
view?.apply {
showProgress(false)
showContent(true)
}
}.launch("login")
}.launch("login")
}
private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> {

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

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,47 +31,45 @@ class LuckyNumberPresenter @Inject constructor(
}
private fun loadData(forceRefresh: Boolean = false) {
flowWithResourceIn {
flatResourceFlow {
val student = studentRepository.getCurrentStudent()
luckyNumberRepository.getLuckyNumber(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Loading lucky number started")
Status.SUCCESS -> {
if (it.data != null) {
Timber.i("Loading lucky number result: Success")
view?.apply {
updateData(it.data)
showContent(true)
showEmpty(false)
showErrorView(false)
}
analytics.logEvent(
"load_item",
"type" to "lucky_number",
"number" to it.data.luckyNumber
)
} else {
Timber.i("Loading lucky number result: No lucky number found")
view?.run {
showContent(false)
showEmpty(true)
showErrorView(false)
}
}
.logResourceStatus("load lucky number")
.onResourceData {
if (it != null) {
view?.apply {
updateData(it)
showContent(true)
showEmpty(false)
showErrorView(false)
}
} else {
view?.run {
showContent(false)
showEmpty(true)
showErrorView(false)
}
}
Status.ERROR -> {
Timber.i("Loading lucky number result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
.onResourceSuccess {
if (it != null) {
analytics.logEvent(
"load_item",
"type" to "lucky_number",
"number" to it.luckyNumber
)
}
}
}.afterLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
.onResourceNotLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}
}.launch()
.onResourceError(errorHandler::dispatch)
.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {

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 {
currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear)
reloadNavigation()
}.launch("holidays")
}
.catch { Timber.i("Loading semester result: An exception occurred") }
.onEach {
currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear)
reloadNavigation()
}
.launch("holidays")
}
private fun loadData() {
flowWithResource {
flow {
val student = studentRepository.getCurrentStudent()
luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday)
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Loading lucky number history started")
Status.SUCCESS -> {
if (!it.data?.first().isNullOrEmpty()) {
Timber.i("Loading lucky number result: Success")
view?.apply {
updateData(it.data!!.first())
showContent(true)
showEmpty(false)
showErrorView(false)
showProgress(false)
}
analytics.logEvent(
"load_items",
"type" to "lucky_number_history",
"numbers" to it.data
)
} else {
Timber.i("Loading lucky number history result: No lucky numbers found")
view?.run {
showContent(false)
showEmpty(true)
showErrorView(false)
}
emitAll(
luckyNumberRepository.getLuckyNumberHistory(
student = student,
start = currentDate.monday,
end = currentDate.sunday
)
)
}
.onEach {
if (!it.isNullOrEmpty()) {
view?.apply {
updateData(it)
showContent(true)
showEmpty(false)
showErrorView(false)
showProgress(false)
}
} else {
view?.run {
showContent(false)
showEmpty(true)
showErrorView(false)
showProgress(false)
}
}
Status.ERROR -> {
Timber.i("Loading lucky number history result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
analytics.logEvent(
"load_items",
"type" to "lucky_number_history",
)
}
}.afterLoading {
view?.run {
showProgress(false)
}
}.launch()
.catch { errorHandler.dispatch(it) }
.launchIn(presenterScope)
}
private fun showErrorViewOnError(message: String, error: Throwable) {
@ -143,8 +127,10 @@ class LuckyNumberHistoryPresenter @Inject constructor(
view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM"))
updateNavigationWeek(
"${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
}
}

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

@ -23,16 +23,7 @@ import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.InAppReviewHelper
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener
import io.github.wulkanowy.utils.*
import timber.log.Timber
import javax.inject.Inject
@ -169,8 +160,12 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
.setIcon(R.drawable.ic_main_more)
}
selectedItemId = startMenuIndex
setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) }
setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) }
setOnItemSelectedListener {
this@MainActivity.presenter.onTabSelected(it.itemId, false)
}
setOnItemReselectedListener {
this@MainActivity.presenter.onTabSelected(it.itemId, true)
}
}
}
@ -178,8 +173,10 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
val fragment =
supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
val fragment = supportFragmentManager.fragmentFactory.instantiate(
classLoader,
pref.fragment.toString()
)
pushView(fragment)
return true
}

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

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