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 same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2021 Wulkanowy Copyright 2022 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 103 versionCode 104
versionName "1.5.0" versionName "1.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -73,6 +73,8 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
} }
debug { debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV" resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"
@ -169,14 +171,14 @@ huaweiPublish {
ext { ext {
work_manager = "2.7.1" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.4.0" room = "2.4.2"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.12.1" mockk = "1.12.2"
coroutines = "1.6.0" coroutines = "1.6.0"
} }
dependencies { 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' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@ -184,19 +186,19 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.7.0" 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.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.0" implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.fragment:fragment-ktx:1.4.0" implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.annotation:annotation:1.3.0" 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.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.2" implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.4.0" implementation "com.google.android.material:material:1.5.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.github.lopspower:CircularImageView:4.2.0' implementation 'com.github.lopspower:CircularImageView:4.2.0'
@ -204,7 +206,7 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$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-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -228,24 +230,25 @@ dependencies {
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:1.4.0" implementation "io.coil-kt:coil:1.4.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.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' 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-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:' 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.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.hms:hianalytics:6.4.1.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.3.200' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' 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 "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk" 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 package io.github.wulkanowy.data
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) { import kotlinx.coroutines.flow.*
companion object { import kotlinx.coroutines.sync.Mutex
fun <T> success(data: T?): Resource<T> { import kotlinx.coroutines.sync.withLock
return Resource(Status.SUCCESS, data, null) import timber.log.Timber
}
fun <T> error(error: Throwable?, data: T? = null): Resource<T> { sealed class Resource<T> {
return Resource(Status.ERROR, data, error)
}
fun <T> loading(data: T? = null): Resource<T> { open class Loading<T> : Resource<T>()
return Resource(Status.LOADING, data, null)
} 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 { fun <T> Flow<Resource<T>>.onResourceLoading(block: suspend () -> Unit) = onEach {
LOADING, if (it is Resource.Loading) {
SUCCESS, block()
ERROR }
}
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 package io.github.wulkanowy.data.db
import android.content.Context import android.content.Context
import androidx.room.AutoMigration import androidx.room.*
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.entities.*
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.migrations.* import io.github.wulkanowy.data.db.migrations.*
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton import javax.inject.Singleton
@ -108,6 +46,7 @@ import javax.inject.Singleton
autoMigrations = [ autoMigrations = [
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47), AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -116,7 +55,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 47 const val VERSION_SCHEMA = 48
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),

View File

@ -1,11 +1,14 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.*
import java.util.*
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.Month import java.time.Month
@ -58,4 +61,11 @@ class Converters {
emptyList() // handle errors from old gson Pair serialized data 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") @Query("SELECT * FROM Students")
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters> 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") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)

View File

@ -24,5 +24,8 @@ data class GradeSemesterStatistics(
var id: Long = 0 var id: Long = 0
@Transient @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.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
import java.time.Instant import java.time.Instant
@Entity(tableName = "Notifications") @Entity(tableName = "Notifications")
@ -18,6 +19,9 @@ data class Notification(
val type: NotificationType, val type: NotificationType,
@ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}")
val destination: Destination,
val date: Instant, val date: Instant,
val data: String? = null val data: String? = null

View File

@ -1,10 +1,10 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import android.content.Intent
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
data class NotificationData( data class NotificationData(
val intentToStart: Intent, val destination: Destination,
val title: String, val title: String,
val content: String val content: String
) )
@ -13,7 +13,7 @@ data class GroupNotificationData(
val notificationDataList: List<NotificationData>, val notificationDataList: List<NotificationData>,
val title: String, val title: String,
val content: String, val content: String,
val intentToStart: Intent, val destination: Destination,
val type: NotificationType 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.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.Student 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.AppInfo
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -19,6 +19,7 @@ class AdminMessageRepository @Inject constructor(
suspend fun getAdminMessages(student: Student) = networkBoundResource( suspend fun getAdminMessages(student: Student) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
query = { adminMessageDao.loadAll() }, query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() }, fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true }, 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
@ -36,6 +37,7 @@ class AttendanceRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk 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 kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -28,6 +32,7 @@ class AttendanceSummaryRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -30,6 +31,7 @@ class CompletedLessonsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk 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.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.Instant import java.time.Instant
@ -32,6 +36,7 @@ class ConferenceRepository @Inject constructor(
startDate: Instant = Instant.EPOCH, startDate: Instant = Instant.EPOCH,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -33,6 +34,7 @@ class ExamRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -36,6 +37,10 @@ class GradeRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, 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) -> shouldFetch = { (details, summaries) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired 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.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk 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 kotlinx.coroutines.sync.Mutex
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -42,6 +46,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = partialMutex, mutex = partialMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(partialCacheKey, semester) key = getRefreshKey(partialCacheKey, semester)
@ -63,20 +68,16 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> { "Wszystkie" -> {
val numerator = items.map { val summaryItem = GradePartialStatistics(
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId, studentId = semester.studentId,
semesterId = semester.semesterId, semesterId = semester.semesterId,
subject = subjectName, subject = subjectName,
classAverage = if (numerator.isEmpty()) "" else numerator.average().let { classAverage = items.map { it.classAverage }.getSummaryAverage(),
"%.2f".format(Locale.FRANCE, it) studentAverage = items.map { it.studentAverage }.getSummaryAverage(),
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)).reversed() )
listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
@ -90,6 +91,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = semesterMutex, mutex = semesterMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(semesterCacheKey, semester) key = getRefreshKey(semesterCacheKey, semester)
@ -112,29 +114,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item -> val itemsWithAverage = items.map { item ->
item.copy().apply { item.copy().apply {
val denominator = item.amounts.sum() val denominator = item.amounts.sum()
average = if (denominator == 0) "" else { classAverage = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount -> (item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount (gradeValue + 1) * amount
}.sum().toDouble() / denominator).let { }.sum().toDouble() / denominator).asAverageString()
"%.2f".format(Locale.FRANCE, it)
}
} }
} }
} }
when (subjectName) { when (subjectName) {
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics( "Wszystkie" -> {
studentId = semester.studentId, val summaryItem = GradeSemesterStatistics(
semesterId = semester.semesterId, studentId = semester.studentId,
subject = subjectName, semesterId = semester.semesterId,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), subject = subjectName,
studentGrade = 0 amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
).apply { studentGrade = 0,
average = itemsWithAverage.mapNotNull { ).apply {
it.average.replace(",", ".").toDoubleOrNull() classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage()
}.average().let { studentAverage = items
"%.2f".format(Locale.FRANCE, it) .mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } }
.average().asAverageString()
} }
}).reversed() listOf(summaryItem) + itemsWithAverage
}
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -147,6 +149,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = pointsMutex, mutex = pointsMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
it.isEmpty() || forceRefresh || isExpired 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> { private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0) val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach { 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -32,6 +33,7 @@ class HomeworkRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) 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.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -29,6 +29,7 @@ class LuckyNumberRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) }, query = { luckyNumberDb.load(student.studentId, now()) },
fetch = { 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.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.*
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.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder 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.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -59,6 +55,7 @@ class MessageRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
): Flow<Resource<List<Message>>> = networkBoundResource( ): Flow<Resource<List<Message>>> = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student, folder) key = getRefreshKey(cacheKey, student, folder)
@ -106,8 +103,9 @@ class MessageRepository @Inject constructor(
message: Message, message: Message,
markAsRead: Boolean = false, markAsRead: Boolean = false,
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource( ): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
isResultEmpty = { it?.message?.content.isNullOrBlank() },
shouldFetch = { 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()}") Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isEmpty() it.message.unread || it.message.content.isEmpty()
}, },
@ -123,7 +121,7 @@ class MessageRepository @Inject constructor(
} }
}, },
saveFetchResult = { old, (downloadedMessage, attachments) -> saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" }) checkNotNull(old) { "Fetched message no longer exist!" }
messagesDb.updateAll(listOf(old.message.apply { messagesDb.updateAll(listOf(old.message.apply {
id = old.message.id id = old.message.id
unread = !markAsRead unread = !markAsRead
@ -153,20 +151,27 @@ class MessageRepository @Inject constructor(
recipients = recipients.mapFromEntities() recipients = recipients.mapFromEntities()
) )
suspend fun deleteMessage(student: Student, message: Message) { suspend fun deleteMessages(student: Student, messages: List<Message>) {
val isDeleted = sdk.init(student).deleteMessages( val folderId = messages.first().folderId
messages = listOf(message.messageId), message.folderId val isDeleted = sdk.init(student)
) .deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
if (message.folderId != MessageFolder.TRASHED.id && isDeleted) { if (folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply { val deletedMessages = messages.map {
id = message.id it.copy(folderId = MessageFolder.TRASHED.id)
content = message.content .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? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { json.decodeFromString(it) } ?.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.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.Sdk 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 kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -30,6 +34,7 @@ class MobileDeviceRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -30,6 +31,7 @@ class NoteRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
getRefreshKey(cacheKey, semester) 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.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -31,6 +31,7 @@ class SchoolAnnouncementRepository @Inject constructor(
forceRefresh: Boolean, notify: Boolean = false forceRefresh: Boolean, notify: Boolean = false
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -30,6 +30,7 @@ class SchoolRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student) 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,6 +25,7 @@ class StudentInfoRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) }, query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = { 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 { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk 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 kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -27,6 +31,7 @@ class SubjectRepository @Inject constructor(
forceRefresh: Boolean = false, forceRefresh: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk 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 kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -27,6 +31,7 @@ class TeacherRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired 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.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
@ -30,6 +31,10 @@ class TimetableRepository @Inject constructor(
private val cacheKey = "timetable" private val cacheKey = "timetable"
enum class TimetableType {
NORMAL, ADDITIONAL
}
fun getTimetable( fun getTimetable(
student: Student, student: Student,
semester: Semester, semester: Semester,
@ -37,9 +42,16 @@ class TimetableRepository @Inject constructor(
end: LocalDate, end: LocalDate,
forceRefresh: Boolean, forceRefresh: Boolean,
refreshAdditional: Boolean = false, refreshAdditional: Boolean = false,
notify: Boolean = false notify: Boolean = false,
timetableType: TimetableType = TimetableType.NORMAL
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = {
when (timetableType) {
TimetableType.NORMAL -> it.lessons.isEmpty()
TimetableType.ADDITIONAL -> it.additional.isEmpty()
}
},
shouldFetch = { (timetable, additional, headers) -> shouldFetch = { (timetable, additional, headers) ->
val refreshKey = getRefreshKey(cacheKey, semester, start, end) val refreshKey = getRefreshKey(cacheKey, semester, start, end)
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) 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 androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R 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.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -59,7 +58,7 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
Timber.d("Receiving intent... ${intent.toUri(0)}") Timber.d("Receiving intent... ${intent.toUri(0)}")
flowWithResource { resourceFlow {
val showStudentName = !studentRepository.isOneUniqueStudent() val showStudentName = !studentRepository.isOneUniqueStudent()
val student = studentRepository.getCurrentStudent(false) val student = studentRepository.getCurrentStudent(false)
val studentId = intent.getIntExtra(STUDENT_ID, 0) val studentId = intent.getIntExtra(STUDENT_ID, 0)
@ -69,9 +68,9 @@ class TimetableNotificationReceiver : BroadcastReceiver() {
} else { } else {
Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
} }
}.onEach { }
if (it.status == Status.ERROR) Timber.e(it.error!!) .onResourceError { Timber.e(it) }
}.launchIn(GlobalScope) .launchIn(GlobalScope)
} }
private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) { private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) {

View File

@ -15,6 +15,9 @@ import javax.inject.Singleton
@Singleton @Singleton
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { 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( private val destinations = mapOf(
"grade" to Destination.Grade, "grade" to Destination.Grade,
"attendance" to Destination.Attendance, "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.pojos.NotificationData
import io.github.wulkanowy.data.repositories.NotificationRepository import io.github.wulkanowy.data.repositories.NotificationRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.PendingIntentCompat
import io.github.wulkanowy.utils.getCompatBitmap import io.github.wulkanowy.utils.getCompatBitmap
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
@ -47,7 +48,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
notificationData.intentToStart, SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -92,7 +93,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
notificationData.intentToStart, SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -146,7 +147,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
groupNotificationData.intentToStart, SplashActivity.getStartIntent(context, groupNotificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -168,6 +169,7 @@ class AppNotificationManager @Inject constructor(
studentId = student.id, studentId = student.id,
title = notificationData.title, title = notificationData.title,
content = notificationData.content, content = notificationData.content,
destination = notificationData.destination,
type = notificationType, type = notificationType,
date = Instant.now(), 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.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination 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.getPlural
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.Instant import java.time.Instant
@ -23,8 +22,9 @@ class ChangeTimetableNotification @Inject constructor(
suspend fun notify(items: List<Timetable>, student: Student) { suspend fun notify(items: List<Timetable>, student: Student) {
val currentTime = Instant.now() val currentTime = Instant.now()
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
val notificationDataList = changedLessons.groupBy { it.date } val lessonsByDate = changedLessons.groupBy { it.date }
.map { (date, lessons) -> val notificationDataList = lessonsByDate
.flatMap { (date, lessons) ->
getNotificationContents(date, lessons).map { getNotificationContents(date, lessons).map {
NotificationData( NotificationData(
title = context.getPlural( title = context.getPlural(
@ -32,14 +32,10 @@ class ChangeTimetableNotification @Inject constructor(
1 1
), ),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent( destination = Destination.Timetable(date)
context = context,
destination = Destination.Timetable(date)
)
) )
} }
} }
.flatten()
.ifEmpty { return } .ifEmpty { return }
val groupNotificationData = GroupNotificationData( val groupNotificationData = GroupNotificationData(
@ -53,7 +49,7 @@ class ChangeTimetableNotification @Inject constructor(
changedLessons.size, changedLessons.size,
changedLessons.size changedLessons.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()), destination = Destination.Timetable(lessonsByDate.toSortedMap().firstKey()),
type = NotificationType.CHANGE_TIMETABLE type = NotificationType.CHANGE_TIMETABLE
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,10 +21,7 @@ class NewSchoolAnnouncementNotification @Inject constructor(
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) { suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
val notificationDataList = items.map { val notificationDataList = items.map {
NotificationData( NotificationData(
intentToStart = SplashActivity.getStartIntent( destination = Destination.SchoolAnnouncement,
context = context,
destination = Destination.SchoolAnnouncement
),
title = context.getPlural( title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title, R.plurals.school_announcement_notify_new_item_title,
1 1
@ -34,10 +31,7 @@ class NewSchoolAnnouncementNotification @Inject constructor(
} }
val groupNotificationData = GroupNotificationData( val groupNotificationData = GroupNotificationData(
type = NotificationType.NEW_ANNOUNCEMENT, type = NotificationType.NEW_ANNOUNCEMENT,
intentToStart = SplashActivity.getStartIntent( destination = Destination.SchoolAnnouncement,
context = context,
destination = Destination.SchoolAnnouncement
),
title = context.getPlural( title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title, R.plurals.school_announcement_notify_new_item_title,
items.size 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.data.waitForResult
import javax.inject.Inject import javax.inject.Inject
class AttendanceSummaryWork @Inject constructor( 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.AttendanceRepository 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.services.sync.notifications.NewAttendanceNotification
import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.previousOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.CompletedLessonsRepository import io.github.wulkanowy.data.repositories.CompletedLessonsRepository
import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ConferenceRepository 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.services.sync.notifications.NewConferenceNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.ExamRepository 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.services.sync.notifications.NewExamNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.GradeStatisticsRepository import io.github.wulkanowy.data.repositories.GradeStatisticsRepository
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.data.waitForResult
import javax.inject.Inject import javax.inject.Inject
class GradeStatisticsWork @Inject constructor( 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.GradeRepository 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.services.sync.notifications.NewGradeNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.HomeworkRepository 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.services.sync.notifications.NewHomeworkNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.LuckyNumberRepository 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.services.sync.notifications.NewLuckyNumberNotification
import io.github.wulkanowy.utils.waitForResult
import javax.inject.Inject import javax.inject.Inject
class LuckyNumberWork @Inject constructor( 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.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.repositories.MessageRepository 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.services.sync.notifications.NewMessageNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.NoteRepository 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.services.sync.notifications.NewNoteNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository 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.services.sync.notifications.NewSchoolAnnouncementNotification
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import javax.inject.Inject 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.data.repositories.TeacherRepository
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.data.waitForResult
import javax.inject.Inject import javax.inject.Inject
class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work { 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.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.TimetableRepository 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.services.sync.notifications.ChangeTimetableNotification
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject import javax.inject.Inject

View File

@ -1,17 +1,10 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
open class BasePresenter<T : BaseView>( open class BasePresenter<T : BaseView>(
@ -37,28 +30,28 @@ open class BasePresenter<T : BaseView>(
} }
fun onExpiredLoginSelected() { fun onExpiredLoginSelected() {
flowWithResource { Timber.i("Attempt to switch the student after the session expires")
val student = studentRepository.getCurrentStudent(false)
studentRepository.logoutStudent(student)
val students = studentRepository.getSavedStudents(false) presenterScope.launch {
if (students.isNotEmpty()) { runCatching {
Timber.i("Switching current student") val student = studentRepository.getCurrentStudent(false)
studentRepository.switchStudent(students[0]) studentRepository.logoutStudent(student)
val students = studentRepository.getSavedStudents(false)
if (students.isNotEmpty()) {
Timber.i("Switching current student")
studentRepository.switchStudent(students[0])
}
} }
}.onEach { .onFailure {
when (it.status) { Timber.i("Switch student result: An exception occurred")
Status.LOADING -> Timber.i("Attempt to switch the student after the session expires") errorHandler.dispatch(it)
Status.SUCCESS -> { }
.onSuccess {
Timber.i("Switch student result: Open login view") Timber.i("Switch student result: Open login view")
view?.openClearLoginView() 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 { fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules package io.github.wulkanowy.ui.modules
import androidx.fragment.app.Fragment 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.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment 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.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import java.io.Serializable import kotlinx.serialization.Serializable
import java.time.LocalDate 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 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 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) { enum class Type(val defaultDestination: Destination) {
DASHBOARD(Dashboard), DASHBOARD(Dashboard),
@ -43,94 +45,84 @@ sealed interface Destination : Serializable {
MESSAGE(Message); MESSAGE(Message);
} }
object Dashboard : Destination { @Serializable
object Dashboard : Destination() {
override val type get() = Type.DASHBOARD override val type get() = Type.DASHBOARD
override val fragment get() = DashboardFragment.newInstance() override val fragment get() = DashboardFragment.newInstance()
} }
object Grade : Destination { @Serializable
object Grade : Destination() {
override val type get() = Type.GRADE override val type get() = Type.GRADE
override val fragment get() = GradeFragment.newInstance() override val fragment get() = GradeFragment.newInstance()
} }
object Attendance : Destination { @Serializable
object Attendance : Destination() {
override val type get() = Type.ATTENDANCE override val type get() = Type.ATTENDANCE
override val fragment get() = AttendanceFragment.newInstance() override val fragment get() = AttendanceFragment.newInstance()
} }
object Exam : Destination { @Serializable
object Exam : Destination() {
override val type get() = Type.EXAM override val type get() = Type.EXAM
override val fragment get() = ExamFragment.newInstance() 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 type get() = Type.TIMETABLE
override val fragment get() = TimetableFragment.newInstance(date) override val fragment get() = TimetableFragment.newInstance(date)
} }
object Homework : Destination { @Serializable
object Homework : Destination() {
override val type get() = Type.HOMEWORK override val type get() = Type.HOMEWORK
override val fragment get() = HomeworkFragment.newInstance() override val fragment get() = HomeworkFragment.newInstance()
} }
object Note : Destination { @Serializable
object Note : Destination() {
override val type get() = Type.NOTE override val type get() = Type.NOTE
override val fragment get() = NoteFragment.newInstance() override val fragment get() = NoteFragment.newInstance()
} }
object Conference : Destination { @Serializable
object Conference : Destination() {
override val type get() = Type.CONFERENCE override val type get() = Type.CONFERENCE
override val fragment get() = ConferenceFragment.newInstance() override val fragment get() = ConferenceFragment.newInstance()
} }
object SchoolAnnouncement : Destination { @Serializable
object SchoolAnnouncement : Destination() {
override val type get() = Type.SCHOOL_ANNOUNCEMENT override val type get() = Type.SCHOOL_ANNOUNCEMENT
override val fragment get() = SchoolAnnouncementFragment.newInstance() override val fragment get() = SchoolAnnouncementFragment.newInstance()
} }
object School : Destination { @Serializable
object School : Destination() {
override val type get() = Type.SCHOOL override val type get() = Type.SCHOOL
override val fragment get() = SchoolFragment.newInstance() override val fragment get() = SchoolFragment.newInstance()
} }
object LuckyNumber : Destination { @Serializable
object LuckyNumber : Destination() {
override val type get() = Type.LUCKY_NUMBER override val type get() = Type.LUCKY_NUMBER
override val fragment get() = LuckyNumberFragment.newInstance() override val fragment get() = LuckyNumberFragment.newInstance()
} }
object More : Destination { @Serializable
object More : Destination() {
override val type get() = Type.MORE override val type get() = Type.MORE
override val fragment get() = MoreFragment.newInstance() override val fragment get() = MoreFragment.newInstance()
} }
object Message : Destination { @Serializable
object Message : Destination() {
override val type get() = Type.MESSAGE override val type get() = Type.MESSAGE
override val fragment get() = MessageFragment.newInstance() override val fragment get() = MessageFragment.newInstance()
} }
} }

View File

@ -1,13 +1,11 @@
package io.github.wulkanowy.ui.modules.about.contributor 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.pojos.Contributor
import io.github.wulkanowy.data.repositories.AppCreatorRepository import io.github.wulkanowy.data.repositories.AppCreatorRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject import javax.inject.Inject
class ContributorPresenter @Inject constructor( class ContributorPresenter @Inject constructor(
@ -31,15 +29,11 @@ class ContributorPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { appCreatorRepository.getAppCreators() }.onEach { resourceFlow { appCreatorRepository.getAppCreators() }
when (it.status) { .onResourceLoading { view?.showProgress(true) }
Status.LOADING -> view?.showProgress(true) .onResourceSuccess { view?.updateData(it) }
Status.SUCCESS -> view?.run { .onResourceNotLoading { view?.showProgress(false) }
showProgress(false) .onResourceError { errorHandler.dispatch(it) }
updateData(it.data!!) .launch()
}
Status.ERROR -> errorHandler.dispatch(it.error!!)
}
}.launch()
} }
} }

View File

@ -1,16 +1,12 @@
package io.github.wulkanowy.ui.modules.about.license package io.github.wulkanowy.ui.modules.about.license
import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.entity.Library
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.afterLoading import kotlinx.coroutines.launch
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class LicensePresenter @Inject constructor( class LicensePresenter @Inject constructor(
@ -30,18 +26,16 @@ class LicensePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { presenterScope.launch {
withContext(dispatchers.io) { runCatching {
view?.appLibraries.orEmpty() withContext(dispatchers.io) {
view?.appLibraries.orEmpty()
}
} }
}.onEach { .onFailure { errorHandler.dispatch(it) }
when (it.status) { .onSuccess { view?.updateData(it) }
Status.LOADING -> Timber.d("License data load started")
Status.SUCCESS -> view?.updateData(it.data!!)
Status.ERROR -> errorHandler.dispatch(it.error!!)
}
}.afterLoading {
view?.showProgress(false) view?.showProgress(false)
}.launch() }
} }
} }

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.account 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.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.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -32,20 +33,10 @@ class AccountPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudents(false) }
.onEach { .logResourceStatus("load account data")
when (it.status) { .onResourceSuccess { view?.updateData(createAccountItems(it)) }
Status.LOADING -> Timber.i("Loading account data started") .onResourceError(errorHandler::dispatch)
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!!)
}
}
}
.launch("load") .launch("load")
} }

View File

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

View File

@ -1,15 +1,12 @@
package io.github.wulkanowy.ui.modules.account.accountedit 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.Student
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AppInfo 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -38,43 +35,26 @@ class AccountEditPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { resourceFlow { studentRepository.getStudentById(student.id, false).avatarColor }
studentRepository.getStudentById(student.id, false).avatarColor .logResourceStatus("load student")
}.onEach { resource -> .onResourceSuccess { view?.updateSelectedColorData(it.toInt()) }
when (resource.status) { .onResourceError(errorHandler::dispatch)
Status.LOADING -> Timber.i("Attempt to load student") .launch("load_data")
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")
} }
fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) { fun changeStudentNickAndAvatar(nick: String, avatarColor: Int) {
flowWithResource { resourceFlow {
val studentNick = val studentNick = StudentNickAndAvatar(
StudentNickAndAvatar(nick = nick.trim(), avatarColor = avatarColor.toLong()) nick = nick.trim(),
.apply { id = student.id } avatarColor = avatarColor.toLong()
).apply { id = student.id }
studentRepository.updateStudentNickAndAvatar(studentNick) 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") .launch("update_student")
} }
} }

View File

@ -1,14 +1,11 @@
package io.github.wulkanowy.ui.modules.account.accountquick 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.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.account.AccountItem 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -43,21 +40,11 @@ class AccountQuickPresenter @Inject constructor(
return return
} }
flowWithResource { studentRepository.switchStudent(studentWithSemesters) } resourceFlow { studentRepository.switchStudent(studentWithSemesters) }
.onEach { .logResourceStatus("change student")
when (it.status) { .onResourceSuccess { view?.recreateMainView() }
Status.LOADING -> Timber.i("Attempt to change a student") .onResourceNotLoading { view?.popView() }
Status.SUCCESS -> { .onResourceError(errorHandler::dispatch)
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() }
.launch("switch") .launch("switch")
} }

View File

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

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.annotation.SuppressLint 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.db.entities.Attendance
import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository 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.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.*
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 kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -213,93 +202,77 @@ class AttendancePresenter @Inject constructor(
var isParent = false var isParent = false
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
isParent = student.isParent isParent = student.isParent
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
attendanceRepository.getAttendance( attendanceRepository.getAttendance(
student, student = student,
semester, semester = semester,
currentDate, start = currentDate,
currentDate, end = currentDate,
forceRefresh forceRefresh = forceRefresh
) )
}.onEach { }
when (it.status) { .logResourceStatus("load attendance")
Status.LOADING -> { .onResourceLoading {
view?.showExcuseButton(false) view?.showExcuseButton(false)
if (!it.data.isNullOrEmpty()) { }
val filteredAttendance = if (prefRepository.isShowPresent) { .mapResourceData {
it.data if (prefRepository.isShowPresent) {
} else { it
it.data.filter { item -> !item.presence } } else {
} it.filter { item -> !item.presence }
}.sortedBy { item -> item.number }
view?.run { }
enableSwipe(true) .onResourceData {
showRefresh(true) view?.run {
showProgress(false) enableSwipe(true)
showErrorView(false) showProgress(false)
showEmpty(filteredAttendance.isEmpty()) showErrorView(false)
showContent(filteredAttendance.isNotEmpty()) showEmpty(it.isEmpty())
updateData(filteredAttendance.sortedBy { item -> item.number }) showContent(it.isNotEmpty())
} updateData(it)
}
}
Status.SUCCESS -> {
Timber.i("Loading attendance result: Success")
val filteredAttendance = if (prefRepository.isShowPresent) {
it.data.orEmpty()
} else {
it.data?.filter { item -> !item.presence }.orEmpty()
}
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) isVulcanExcusedFunctionEnabled = it.any { item -> item.excusable }
showProgress(false) val anyExcusables = it.any { it.isExcusableOrNotExcused }
enableSwipe(true) 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>) { private fun excuseAbsence(reason: String?, toExcuseList: List<Attendance>) {
flowWithResource { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
}.onEach { }.onEach {
when (it.status) { when (it) {
Status.LOADING -> view?.run { is Resource.Loading -> view?.run {
Timber.i("Excusing absence started") Timber.i("Excusing absence started")
showProgress(true) showProgress(true)
showContent(false) showContent(false)
showExcuseButton(false) showExcuseButton(false)
} }
Status.SUCCESS -> { is Resource.Success -> {
Timber.i("Excusing for absence result: Success") Timber.i("Excusing for absence result: Success")
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size) analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
attendanceToExcuseList.clear() attendanceToExcuseList.clear()
@ -311,9 +284,9 @@ class AttendancePresenter @Inject constructor(
} }
loadData(forceRefresh = true) loadData(forceRefresh = true)
} }
Status.ERROR -> { is Resource.Error -> {
Timber.i("Excusing for absence result: An exception occurred") Timber.i("Excusing for absence result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
loadData() loadData()
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance.summary 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.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 timber.log.Timber
import java.time.Month import java.time.Month
import javax.inject.Inject import javax.inject.Inject
@ -75,11 +72,9 @@ class AttendanceSummaryPresenter @Inject constructor(
} }
private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { private fun loadData(subjectId: Int, forceRefresh: Boolean = false) {
Timber.i("Loading attendance summary data started")
currentSubjectId = subjectId currentSubjectId = subjectId
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
@ -89,47 +84,37 @@ class AttendanceSummaryPresenter @Inject constructor(
subjectId = subjectId, subjectId = subjectId,
forceRefresh = forceRefresh forceRefresh = forceRefresh
) )
}.onEach { }
when (it.status) { .logResourceStatus("load attendance summary")
Status.LOADING -> { .mapResourceData(this::sortItems)
if (!it.data.isNullOrEmpty()) { .onResourceData {
view?.run { view?.run {
enableSwipe(true) enableSwipe(true)
showRefresh(true) showProgress(false)
showProgress(false) showErrorView(false)
showContent(true) showContent(it.isNotEmpty())
showErrorView(false) showEmpty(it.isEmpty())
updateDataSet(sortItems(it.data)) updateDataSet(it)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading attendance summary result: Success")
view?.apply {
showErrorView(false)
showEmpty(it.data!!.isEmpty())
showContent(it.data.isNotEmpty())
updateDataSet(sortItems(it.data))
}
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) analytics.logEvent(
showProgress(false) "load_data",
enableSwipe(true) "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 -> private fun sortItems(items: List<AttendanceSummary>) = items.sortedByDescending { item ->
@ -148,27 +133,20 @@ class AttendanceSummaryPresenter @Inject constructor(
} }
private fun loadSubjects() { private fun loadSubjects() {
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
subjectRepository.getSubjects(student, semester) subjectRepository.getSubjects(student, semester)
}.onEach { }
when (it.status) { .logResourceStatus("load attendance summary subjects")
Status.LOADING -> Timber.i("Loading attendance summary subjects started") .onResourceData {
Status.SUCCESS -> { subjects = it
subjects = it.data!! view?.run {
view?.updateSubjects(it.map { subject -> subject.name }.toList())
Timber.i("Loading attendance summary subjects result: Success") showSubjects(true)
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!!)
} }
} }
}.launch("subjects") .onResourceError(errorHandler::dispatch)
.launch("subjects")
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.conference 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.db.entities.Conference
import io.github.wulkanowy.data.repositories.ConferenceRepository import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.SemesterRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -64,50 +61,39 @@ class ConferencePresenter @Inject constructor(
} }
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading conference data started") flatResourceFlow {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences(student, semester, forceRefresh) conferenceRepository.getConferences(student, semester, forceRefresh)
}.onEach { }
when (it.status) { .logResourceStatus("load conference data")
Status.LOADING -> { .mapResourceData { it.sortedByDescending { conference -> conference.date } }
if (!it.data.isNullOrEmpty()) { .onResourceData {
view?.run { view?.run {
enableSwipe(true) enableSwipe(true)
showRefresh(true) showProgress(false)
showProgress(false) showErrorView(false)
showContent(true) showContent(it.isNotEmpty())
updateData(it.data.sortedByDescending { conference -> conference.date }) showEmpty(it.isEmpty())
} updateData(it)
}
}
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) analytics.logEvent(
showProgress(false) "load_data",
enableSwipe(true) "type" to "conferences",
"items" to it.size
)
} }
}.launch() .onResourceNotLoading {
view?.run {
enableSwipe(true)
showProgress(false)
showRefresh(false)
}
}
.onResourceError(errorHandler::dispatch)
.launch()
} }
} }

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -225,27 +223,26 @@ class DashboardPresenter @Inject constructor(
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles val selectedTiles = preferencesRepository.selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null))
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh) val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
.map { .mapResourceData {
if (it.data == null) { it ?: LuckyNumber(0, LocalDate.now(), 0)
it.copy(data = LuckyNumber(0, LocalDate.now(), 0))
} else it
} }
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null) .takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowSuccess
val messageFLow = messageRepository.getMessages( val messageFLow = messageRepository.getMessages(
student = student, student = student,
semester = semester, semester = semester,
folder = MessageFolder.RECEIVED, folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null) ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary( val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
student = student, student = student,
semester = semester, semester = semester,
subjectId = -1, subjectId = -1,
forceRefresh = forceRefresh forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null) ).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowSuccess
emitAll( emitAll(
combine( combine(
@ -253,16 +250,13 @@ class DashboardPresenter @Inject constructor(
messageFLow, messageFLow,
attendanceFlow attendanceFlow
) { luckyNumberResource, messageResource, attendanceResource -> ) { luckyNumberResource, messageResource, attendanceResource ->
val error = val resList = listOf(luckyNumberResource, messageResource, attendanceResource)
luckyNumberResource?.error ?: messageResource?.error ?: attendanceResource?.error resList.firstNotNullOfOrNull { it.errorOrNull }?.let { throw it }
error?.let { throw it } val isLoading = resList.any { it is Resource.Loading }
val luckyNumber = luckyNumberResource?.data?.luckyNumber val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber
val messageCount = messageResource?.data?.count { it.unread } val messageCount = messageResource.dataOrNull?.count { it.unread }
val attendancePercentage = attendanceResource?.data?.calculatePercentage() val attendancePercentage = attendanceResource.dataOrNull?.calculatePercentage()
val isLoading =
luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING
DashboardItem.HorizontalGroup( DashboardItem.HorizontalGroup(
isLoading = isLoading, isLoading = isLoading,
@ -295,72 +289,69 @@ class DashboardPresenter @Inject constructor(
) )
errorHandler.dispatch(it) errorHandler.dispatch(it)
} }
.launch("horizontal_group") .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}")
} }
private fun loadGrades(student: Student, forceRefresh: Boolean) { private fun loadGrades(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
gradeRepository.getGrades(student, semester, forceRefresh) gradeRepository.getGrades(student, semester, forceRefresh)
}.map { originalResource -> }
val filteredSubjectWithGrades = originalResource.data?.first .mapResourceData { (details, _) ->
.orEmpty() val filteredSubjectWithGrades = details
.filter { it.date >= LocalDate.now().minusDays(7) } .filter { it.date >= LocalDate.now().minusDays(7) }
.groupBy { it.subject } .groupBy { it.subject }
.mapValues { entry -> .mapValues { entry ->
entry.value entry.value
.take(5) .take(5)
.sortedByDescending { it.date } .sortedByDescending { it.date }
} }
.toList() .toList()
.sortedByDescending { (_, grades) -> grades[0].date } .sortedByDescending { (_, grades) -> grades[0].date }
.toMap() .toMap()
Resource( filteredSubjectWithGrades
status = originalResource.status, }
data = filteredSubjectWithGrades.takeIf { originalResource.data != null }, .onEach {
error = originalResource.error when (it) {
) is Resource.Loading -> {
}.onEach { Timber.i("Loading dashboard grades data started")
when (it.status) { if (forceRefresh) return@onEach
Status.LOADING -> { updateData(
Timber.i("Loading dashboard grades data started") DashboardItem.Grades(
if (forceRefresh) return@onEach subjectWithGrades = it.dataOrNull,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
), forceRefresh
)
updateData( if (!it.dataOrNull.isNullOrEmpty()) {
DashboardItem.Grades( firstLoadedItemList += DashboardItem.Type.GRADES
subjectWithGrades = it.data, }
gradeTheme = preferencesRepository.gradeColorTheme, }
isLoading = true is Resource.Success -> {
), forceRefresh Timber.i("Loading dashboard grades result: Success")
) updateData(
DashboardItem.Grades(
if (!it.data.isNullOrEmpty()) { subjectWithGrades = it.data,
firstLoadedItemList += DashboardItem.Type.GRADES 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) { private fun loadLessons(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay val date = LocalDate.now().nextOrSameSchoolDay
@ -371,40 +362,41 @@ class DashboardPresenter @Inject constructor(
end = date.plusDays(1), end = date.plusDays(1),
forceRefresh = forceRefresh 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 { if (!it.dataOrNull?.lessons.isNullOrEmpty()) {
when (it.status) { firstLoadedItemList += DashboardItem.Type.LESSONS
Status.LOADING -> { }
Timber.i("Loading dashboard lessons data started") }
if (forceRefresh) return@onEach is Resource.Success -> {
updateData( Timber.i("Loading dashboard lessons result: Success")
DashboardItem.Lessons(it.data, isLoading = true), updateData(
forceRefresh DashboardItem.Lessons(it.data), forceRefresh
) )
}
if (!it.data?.lessons.isNullOrEmpty()) { is Resource.Error -> {
firstLoadedItemList += DashboardItem.Type.LESSONS 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) { private fun loadHomework(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay val date = LocalDate.now().nextOrSameSchoolDay
@ -415,73 +407,79 @@ class DashboardPresenter @Inject constructor(
end = date, end = date,
forceRefresh = forceRefresh forceRefresh = forceRefresh
) )
}.map { homeworkResource -> }
val currentDate = LocalDate.now() .mapResourceData { homework ->
val currentDate = LocalDate.now()
val filteredHomework = homeworkResource.data val filteredHomework = homework.filter {
?.filter { (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone } (it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone
?.sortedBy { it.date } }.sortedBy { it.date }
homeworkResource.copy(data = filteredHomework) filteredHomework
}.onEach { }
when (it.status) { .onEach {
Status.LOADING -> { when (it) {
Timber.i("Loading dashboard homework data started") is Resource.Loading -> {
if (forceRefresh) return@onEach Timber.i("Loading dashboard homework data started")
updateData( if (forceRefresh) return@onEach
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true), val data = it.dataOrNull.orEmpty()
forceRefresh updateData(
) DashboardItem.Homework(data, isLoading = true),
forceRefresh
)
if (!it.data.isNullOrEmpty()) { if (data.isNotEmpty()) {
firstLoadedItemList += DashboardItem.Type.HOMEWORK 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) { private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh) schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
}.onEach { }
when (it.status) { .onEach {
Status.LOADING -> { when (it) {
Timber.i("Loading dashboard announcements data started") is Resource.Loading -> {
if (forceRefresh) return@onEach Timber.i("Loading dashboard announcements data started")
updateData( if (forceRefresh) return@onEach
DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true), updateData(
forceRefresh DashboardItem.Announcements(it.dataOrNull.orEmpty(), isLoading = true),
) forceRefresh
)
if (!it.data.isNullOrEmpty()) { if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS 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) { private fun loadExams(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams( examRepository.getExams(
@ -492,40 +490,37 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh forceRefresh = forceRefresh
) )
} }
.map { examResource -> .mapResourceData { exams -> exams.sortedBy { exam -> exam.date } }
val sortedExams = examResource.data?.sortedBy { it.date }
examResource.copy(data = sortedExams)
}
.onEach { .onEach {
when (it.status) { when (it) {
Status.LOADING -> { is Resource.Loading -> {
Timber.i("Loading dashboard exams data started") Timber.i("Loading dashboard exams data started")
if (forceRefresh) return@onEach if (forceRefresh) return@onEach
updateData( updateData(
DashboardItem.Exams(it.data.orEmpty(), isLoading = true), DashboardItem.Exams(it.dataOrNull.orEmpty(), isLoading = true),
forceRefresh forceRefresh
) )
if (!it.data.isNullOrEmpty()) { if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.EXAMS firstLoadedItemList += DashboardItem.Type.EXAMS
} }
} }
Status.SUCCESS -> { is Resource.Success -> {
Timber.i("Loading dashboard exams result: 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") Timber.i("Loading dashboard exams result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
updateData(DashboardItem.Exams(error = it.error), forceRefresh) updateData(DashboardItem.Exams(error = it.error), forceRefresh)
} }
} }
}.launch("dashboard_exams") }
.launchWithUniqueRefreshJob("dashboard_exams", forceRefresh)
} }
private fun loadConferences(student: Student, forceRefresh: Boolean) { private fun loadConferences(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { flatResourceFlow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences( conferenceRepository.getConferences(
@ -534,59 +529,62 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh, forceRefresh = forceRefresh,
startDate = Instant.now(), startDate = Instant.now(),
) )
}.onEach { }
when (it.status) { .onEach {
Status.LOADING -> { when (it) {
Timber.i("Loading dashboard conferences data started") is Resource.Loading -> {
if (forceRefresh) return@onEach Timber.i("Loading dashboard conferences data started")
updateData( if (forceRefresh) return@onEach
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true), updateData(
forceRefresh DashboardItem.Conferences(it.dataOrNull.orEmpty(), isLoading = true),
) forceRefresh
)
if (!it.data.isNullOrEmpty()) { if (!it.dataOrNull.isNullOrEmpty()) {
firstLoadedItemList += DashboardItem.Type.CONFERENCES 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) { private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { adminMessageRepository.getAdminMessages(student) } flatResourceFlow { adminMessageRepository.getAdminMessages(student) }
.map { .filter {
val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds val data = it.dataOrNull ?: return@filter true
it.copy(data = it.data.takeUnless { isDismissed }) val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds
!isDismissed
} }
.onEach { .onEach {
when (it.status) { when (it) {
Status.LOADING -> { is Resource.Loading -> {
Timber.i("Loading dashboard admin message data started") Timber.i("Loading dashboard admin message data started")
if (forceRefresh) return@onEach if (forceRefresh) return@onEach
updateData(DashboardItem.AdminMessages(), forceRefresh) updateData(DashboardItem.AdminMessages(), forceRefresh)
} }
Status.SUCCESS -> { is Resource.Success -> {
Timber.i("Loading dashboard admin message result: Success") Timber.i("Loading dashboard admin message result: Success")
updateData( updateData(
dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data), dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data),
forceRefresh = forceRefresh forceRefresh = forceRefresh
) )
} }
Status.ERROR -> { is Resource.Error -> {
Timber.i("Loading dashboard admin message result: An exception occurred") Timber.i("Loading dashboard admin message result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
updateData( updateData(
dashboardItem = DashboardItem.AdminMessages( dashboardItem = DashboardItem.AdminMessages(
adminMessage = it.data, adminMessage = null,
error = it.error error = it.error
), ),
forceRefresh = forceRefresh 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) { private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
@ -733,4 +731,18 @@ class DashboardPresenter @Inject constructor(
dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal 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 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.LoggerRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -23,19 +23,21 @@ class LogViewerPresenter @Inject constructor(
} }
fun onShareLogsSelected(): Boolean { fun onShareLogsSelected(): Boolean {
flowWithResource { loggerRepository.getLogFiles() }.onEach { resourceFlow { loggerRepository.getLogFiles() }
when (it.status) { .onEach {
Status.LOADING -> Timber.d("Loading logs files started") when (it) {
Status.SUCCESS -> { is Resource.Loading -> Timber.d("Loading logs files started")
Timber.i("Loading logs files result: ${it.data!!.joinToString { file -> file.name }}") is Resource.Success -> {
view?.shareLogs(it.data) 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") is Resource.Error -> {
errorHandler.dispatch(it.error!!) Timber.i("Loading logs files result: An exception occurred")
errorHandler.dispatch(it.error)
}
} }
} }
}.launch("share") .launch("share")
return true return true
} }
@ -44,18 +46,20 @@ class LogViewerPresenter @Inject constructor(
} }
private fun loadLogFile() { private fun loadLogFile() {
flowWithResource { loggerRepository.getLastLogLines() }.onEach { resourceFlow { loggerRepository.getLastLogLines() }
when (it.status) { .onEach {
Status.LOADING -> Timber.d("Loading last log file started") when (it) {
Status.SUCCESS -> { is Resource.Loading -> Timber.d("Loading last log file started")
Timber.i("Loading last log file result: load ${it.data!!.size} lines") is Resource.Success -> {
view?.setLines(it.data) 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") is Resource.Error -> {
errorHandler.dispatch(it.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, diaryId = 0,
date = LocalDate.now().minusDays(Random.nextLong(0, 8)), date = LocalDate.now().minusDays(Random.nextLong(0, 8)),
number = 1, number = 1,
start = Instant.now(), start = Instant.now().plus(Duration.ofHours(1)),
end = Instant.now().plus(Duration.ofHours(1)), end = Instant.now(),
subjectOld = "", subjectOld = "",
group = "", group = "",
room = room, room = room,

View File

@ -5,10 +5,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.databinding.DialogExamBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openCalendarEventAdd
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime
class ExamDialog : DialogFragment() { class ExamDialog : DialogFragment() {
@ -46,10 +49,21 @@ class ExamDialog : DialogFragment() {
examDialogSubjectValue.text = exam.subject examDialogSubjectValue.text = exam.subject
examDialogTypeValue.text = exam.type examDialogTypeValue.text = exam.type
examDialogTeacherValue.text = exam.teacher examDialogTeacherValue.text = exam.teacher
examDialogDateValue.text = exam.entryDate.toFormattedString() examDialogEntryDateValue.text = exam.entryDate.toFormattedString()
examDialogDescriptionValue.text = exam.description examDialogDeadlineDateValue.text = exam.date.toFormattedString()
examDialogDescriptionValue.text = exam.description.ifBlank {
getString(R.string.all_no_data)
}
examDialogClose.setOnClickListener { dismiss() } 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 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.db.entities.Exam
import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.*
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 kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -86,61 +78,57 @@ class ExamPresenter @Inject constructor(
flow { flow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
emit(semesterRepository.getCurrentSemester(student)) emit(semesterRepository.getCurrentSemester(student))
}.catch { }
Timber.i("Loading semester result: An exception occurred") .catch { Timber.i("Loading semester result: An exception occurred") }
}.onEach { .onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate currentDate = baseDate
reloadNavigation() reloadNavigation()
}.launch("holidays") }
.launch("holidays")
} }
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading exam data started") flatResourceFlow {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams(student, semester, currentDate.monday, currentDate.sunday, forceRefresh) examRepository.getExams(
}.onEach { student = student,
when (it.status) { semester = semester,
Status.LOADING -> { start = currentDate.monday,
if (!it.data.isNullOrEmpty()) { end = currentDate.sunday,
view?.run { forceRefresh = forceRefresh
enableSwipe(true) )
showRefresh(true) }
showProgress(false) .logResourceStatus("load exam data")
showContent(true) .mapResourceData { createExamItems(it) }
updateData(createExamItems(it.data)) .onResourceData {
} view?.run {
} enableSwipe(true)
} showProgress(false)
Status.SUCCESS -> { showErrorView(false)
Timber.i("Loading exam result: Success") showContent(it.isNotEmpty())
view?.apply { showEmpty(it.isEmpty())
updateData(createExamItems(it.data!!)) updateData(it)
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) analytics.logEvent(
showProgress(false) "load_data",
enableSwipe(true) "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) { private fun showErrorViewOnError(message: String, error: Throwable) {
@ -181,8 +169,10 @@ class ExamPresenter @Inject constructor(
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays) showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + updateNavigationWeek(
currentDate.sunday.toFormattedString("dd.MM")) "${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
} }
} }
} }

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.ui.modules.grade package io.github.wulkanowy.ui.modules.grade
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester 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.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.*
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ONE_SEMESTER
import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.calcAverage
import io.github.wulkanowy.utils.changeModifier import io.github.wulkanowy.utils.changeModifier
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import javax.inject.Inject import javax.inject.Inject
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
@ -37,7 +32,7 @@ class GradeAverageProvider @Inject constructor(
private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage private val isOptionalArithmeticAverage get() = preferencesRepository.isOptionalArithmeticAverage
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) = fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean) =
flowWithResourceIn { flatResourceFlow {
val semesters = semesterRepository.getSemesters(student) val semesters = semesterRepository.getSemesters(student)
when (preferencesRepository.gradeAverageMode) { when (preferencesRepository.gradeAverageMode) {
@ -83,17 +78,17 @@ class GradeAverageProvider @Inject constructor(
val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh) val firstSemesterGradeSubjects = getGradeSubjects(student, firstSemester, forceRefresh)
return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject -> return selectedSemesterGradeSubjects.combine(firstSemesterGradeSubjects) { secondSemesterGradeSubject, firstSemesterGradeSubject ->
if (firstSemesterGradeSubject.status == Status.ERROR) { if (firstSemesterGradeSubject.errorOrNull != null) {
return@combine firstSemesterGradeSubject return@combine firstSemesterGradeSubject
} }
val isAnyVulcanAverageInFirstSemester = val isAnyVulcanAverageInFirstSemester =
firstSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } firstSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage }
val isAnyVulcanAverageInSecondSemester = val isAnyVulcanAverageInSecondSemester =
secondSemesterGradeSubject.data.orEmpty().any { it.isVulcanAverage } secondSemesterGradeSubject.dataOrNull.orEmpty().any { it.isVulcanAverage }
val updatedData = secondSemesterGradeSubject.data?.map { secondSemesterSubject -> val updatedData = secondSemesterGradeSubject.dataOrNull?.map { secondSemesterSubject ->
val firstSemesterSubject = firstSemesterGradeSubject.data.orEmpty() val firstSemesterSubject = firstSemesterGradeSubject.dataOrNull.orEmpty()
.singleOrNull { it.subject == secondSemesterSubject.subject } .singleOrNull { it.subject == secondSemesterSubject.subject }
val updatedAverage = if (averageMode == ALL_YEAR) { val updatedAverage = if (averageMode == ALL_YEAR) {
@ -115,7 +110,7 @@ class GradeAverageProvider @Inject constructor(
} }
secondSemesterSubject.copy(average = updatedAverage) secondSemesterSubject.copy(average = updatedAverage)
} }
secondSemesterGradeSubject.copy(data = updatedData) secondSemesterGradeSubject.mapData { updatedData!! }
} }
} }
@ -144,20 +139,20 @@ class GradeAverageProvider @Inject constructor(
isGradeAverageForceCalc: Boolean, isGradeAverageForceCalc: Boolean,
secondSemesterSubject: GradeSubject, secondSemesterSubject: GradeSubject,
firstSemesterSubject: GradeSubject? firstSemesterSubject: GradeSubject?
): Double { ): Double = if (!isAnyVulcanAverage || isGradeAverageForceCalc) {
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1 val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
return if (!isAnyVulcanAverage || isGradeAverageForceCalc) { val secondSemesterAverage = secondSemesterSubject.grades.updateModifiers(student)
val secondSemesterAverage = .calcAverage(isOptionalArithmeticAverage)
secondSemesterSubject.grades.updateModifiers(student) val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
.calcAverage(isOptionalArithmeticAverage) ?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
val firstSemesterAverage = firstSemesterSubject?.grades?.updateModifiers(student)
?.calcAverage(isOptionalArithmeticAverage) ?: secondSemesterAverage
(secondSemesterAverage + firstSemesterAverage) / divider (secondSemesterAverage + firstSemesterAverage) / divider
} else { } else {
(secondSemesterSubject.average + (firstSemesterSubject?.average ?: secondSemesterSubject.average)) / divider val divider = if (secondSemesterSubject.average > 0) 2 else 1
}
(secondSemesterSubject.average + (firstSemesterSubject?.average
?: secondSemesterSubject.average)) / divider
} }
private fun getGradeSubjects( private fun getGradeSubjects(
@ -168,17 +163,17 @@ class GradeAverageProvider @Inject constructor(
val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc val isGradeAverageForceCalc = preferencesRepository.gradeAverageForceCalc
return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh) return gradeRepository.getGrades(student, semester, forceRefresh = forceRefresh)
.map { res -> .mapResourceData { res ->
val (details, summaries) = res.data ?: null to null val (details, summaries) = res
val isAnyAverage = summaries.orEmpty().any { it.average != .0 } val isAnyAverage = summaries.any { it.average != .0 }
val allGrades = details.orEmpty().groupBy { it.subject } val allGrades = details.groupBy { it.subject }
val items = summaries?.emulateEmptySummaries( val items = summaries.emulateEmptySummaries(
student = student, student = student,
semester = semester, semester = semester,
grades = allGrades.toList(), grades = allGrades.toList(),
calcAverage = isAnyAverage calcAverage = isAnyAverage
)?.map { summary -> ).map { summary ->
val grades = allGrades[summary.subject].orEmpty() val grades = allGrades[summary.subject].orEmpty()
GradeSubject( GradeSubject(
subject = summary.subject, 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 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.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.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCurrentOrLast import io.github.wulkanowy.utils.getCurrentOrLast
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -99,32 +99,26 @@ class GradePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
semesterRepository.getSemesters(student, refreshOnNoCurrent = true) semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
}.onEach { }
when (it.status) { .logResourceStatus("load grade data")
Status.LOADING -> Timber.i("Loading grade data started") .onResourceData {
Status.SUCCESS -> { val current = it.getCurrentOrLast()
val current = it.data!!.getCurrentOrLast() selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex schoolYear = current.schoolYear
schoolYear = current.schoolYear semesters = it.filter { semester -> semester.diaryId == current.diaryId }
semesters = it.data.filter { semester -> semester.diaryId == current.diaryId } view?.setCurrentSemesterName(current.semesterName, schoolYear)
view?.setCurrentSemesterName(current.semesterName, schoolYear) view?.run {
Timber.i("Loading grade data: Attempt load index $currentPageIndex")
view?.run { loadChild(currentPageIndex)
Timber.i("Loading grade result: Attempt load index $currentPageIndex") showErrorView(false)
loadChild(currentPageIndex) showSemesterSwitch(true)
showErrorView(false)
showSemesterSwitch(true)
}
}
Status.ERROR -> {
Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it.error!!)
} }
} }
}.launch() .onResourceError(errorHandler::dispatch)
.launch()
} }
private fun showErrorViewOnError(message: String, error: Throwable) { 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.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.databinding.DialogGradeBinding
import io.github.wulkanowy.utils.colorStringId import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getGradeColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString
class GradeDetailsDialog : DialogFragment() { class GradeDetailsDialog : DialogFragment() {
@ -80,9 +77,7 @@ class GradeDetailsDialog : DialogFragment() {
setBackgroundResource(grade.getBackgroundColor(gradeColorTheme)) setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
} }
gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) }
getString(R.string.all_no_data)
} else grade.teacher
gradeDialogDescriptionValue.text = grade.run { gradeDialogDescriptionValue.text = grade.run {
when { when {

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details 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.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC 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.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.ui.modules.grade.GradeSubject
import io.github.wulkanowy.utils.AnalyticsHelper 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.catch
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -71,7 +67,7 @@ class GradeDetailsPresenter @Inject constructor(
} }
fun onMarkAsReadSelected(): Boolean { fun onMarkAsReadSelected(): Boolean {
flowWithResource { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semesters = semesterRepository.getSemesters(student) val semesters = semesterRepository.getSemesters(student)
val semester = semesters.first { item -> item.semesterId == currentSemesterId } val semester = semesters.first { item -> item.semesterId == currentSemesterId }
@ -79,19 +75,11 @@ class GradeDetailsPresenter @Inject constructor(
Timber.i("Mark as read ${unreadGrades.size} grades") Timber.i("Mark as read ${unreadGrades.size} grades")
gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } }) gradeRepository.updateGrades(unreadGrades.map { it.apply { isRead = true } })
}.onEach { }
when (it.status) { .logResourceStatus("mark grades as read")
Status.LOADING -> Timber.i("Select mark grades as read") .onResourceSuccess { loadData(currentSemesterId, false) }
Status.SUCCESS -> { .onResourceError(errorHandler::dispatch)
Timber.i("Mark as read result: Success") .launch("mark")
loadData(currentSemesterId, false)
}
Status.ERROR -> {
Timber.i("Mark as read result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("mark")
return true return true
} }
@ -138,71 +126,49 @@ class GradeDetailsPresenter @Inject constructor(
} }
private fun loadData(semesterId: Int, forceRefresh: Boolean) { private fun loadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade details data started") flatResourceFlow {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh)
}.onEach { }
Timber.d("Loading grade details status: ${it.status}, data: ${it.data != null}") .logResourceStatus("load grade details")
when (it.status) { .onResourceData {
Status.LOADING -> { view?.run {
val items = createGradeItems(it.data.orEmpty()) enableSwipe(true)
if (items.isNotEmpty()) { showProgress(false)
Timber.i("Loading grade details result: load cached data") showErrorView(false)
view?.run { showContent(it.isNotEmpty())
updateNewGradesAmount(it.data.orEmpty()) showEmpty(it.isEmpty())
enableSwipe(true) updateNewGradesAmount(it)
showRefresh(true)
showProgress(false)
showEmpty(false)
showContent(true)
updateData(
data = items,
expandMode = preferencesRepository.gradeExpandMode,
gradeColorTheme = preferencesRepository.gradeColorTheme
)
notifyParentDataLoaded(semesterId)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading grade details result: Success")
updateNewGradesAmount(it.data!!)
updateMarkAsDoneButton() updateMarkAsDoneButton()
val items = createGradeItems(it.data) updateData(
view?.run { data = createGradeItems(it),
showEmpty(items.isEmpty()) expandMode = preferencesRepository.gradeExpandMode,
showErrorView(false) preferencesRepository.gradeColorTheme
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
) )
} }
Status.ERROR -> { }
Timber.i("Loading grade details result: An exception occurred") .onResourceIntermediate { view?.showRefresh(true) }
errorHandler.dispatch(it.error!!) .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 { .catch {
view?.run { errorHandler.dispatch(it)
showRefresh(false) view?.notifyParentDataLoaded(semesterId)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
} }
}.catch { .onResourceError(errorHandler::dispatch)
errorHandler.dispatch(it) .launch()
view?.notifyParentDataLoaded(semesterId)
}.launch()
} }
private fun updateNewGradesAmount(grades: List<GradeSubject>) { private fun updateNewGradesAmount(grades: List<GradeSubject>) {
@ -267,15 +233,9 @@ class GradeDetailsPresenter @Inject constructor(
} }
private fun updateGrade(grade: Grade) { private fun updateGrade(grade: Grade) {
flowWithResource { gradeRepository.updateGrade(grade) }.onEach { resourceFlow { gradeRepository.updateGrade(grade) }
when (it.status) { .logResourceStatus("update grade result ${grade.id}")
Status.LOADING -> Timber.i("Attempt to update grade ${grade.id}") .onResourceError(errorHandler::dispatch)
Status.SUCCESS -> Timber.i("Update grade result: Success") .launch("update")
Status.ERROR -> {
Timber.i("Update grade result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.launch("update")
} }
} }

View File

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

View File

@ -1,19 +1,12 @@
package io.github.wulkanowy.ui.modules.grade.statistics 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.db.entities.Subject
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.data.repositories.GradeStatisticsRepository import io.github.wulkanowy.data.repositories.*
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.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -125,33 +118,26 @@ class GradeStatisticsPresenter @Inject constructor(
} }
private fun loadSubjects() { private fun loadSubjects() {
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
subjectRepository.getSubjects(student, semester) subjectRepository.getSubjects(student, semester)
}.onEach { }
when (it.status) { .logResourceStatus("load grade stats subjects")
Status.LOADING -> Timber.i("Loading grade stats subjects started") .onResourceData {
Status.SUCCESS -> { subjects = it
subjects = requireNotNull(it.data) view?.run {
Timber.i("Loading grade stats subjects result: Success") showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
updateSubjects(
view?.run { data = it.map { subject -> subject.name },
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) selectedIndex = it.indexOfFirst { subject ->
updateSubjects( subject.name == currentSubjectName
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!!)
} }
} }
}.launch("subjects") .onResourceError(errorHandler::dispatch)
.launch("subjects")
} }
private fun loadDataByType( private fun loadDataByType(
@ -168,7 +154,7 @@ class GradeStatisticsPresenter @Inject constructor(
else -> subjectName else -> subjectName
} }
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semesters = semesterRepository.getSemesters(student) val semesters = semesterRepository.getSemesters(student)
val semester = semesters.first { item -> item.semesterId == semesterId } val semester = semesters.first { item -> item.semesterId == semesterId }
@ -201,58 +187,43 @@ class GradeStatisticsPresenter @Inject constructor(
} }
} }
} }
}.onEach { }
when (it.status) { .logResourceStatus("load grade stats data")
Status.LOADING -> { .mapResourceData {
val isNoContent = it.data == null || checkIsNoContent(it.data, type) val isNoContent = checkIsNoContent(it, type)
if (!isNoContent) { if (isNoContent) emptyList() else it
view?.run { }
showEmpty(isNoContent) .onResourceData {
showErrorView(false) view?.run {
enableSwipe(true) enableSwipe(true)
showRefresh(true) showProgress(false)
showProgress(false) showErrorView(false)
updateData( showEmpty(it.isEmpty())
newItems = if (isNoContent) emptyList() else it.data!!, updateData(
newTheme = preferencesRepository.gradeColorTheme, newItems = it,
showAllSubjectsOnStatisticsList = preferencesRepository.showAllSubjectsOnStatisticsList, 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
) )
} }
Status.ERROR -> { }
Timber.i("Loading grade stats result: An exception occurred") .onResourceIntermediate { view?.showRefresh(true) }
errorHandler.dispatch(it.error!!) .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 { .onResourceError(errorHandler::dispatch)
view?.run { .launch("load")
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
}
}.launch("load")
} }
private fun checkIsNoContent( private fun checkIsNoContent(
@ -267,7 +238,8 @@ class GradeStatisticsPresenter @Inject constructor(
items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0 items.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0
} }
GradeStatisticsItem.DataType.POINTS -> { 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 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.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter 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.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.ui.modules.grade.GradeSubject
import io.github.wulkanowy.utils.AnalyticsHelper 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -37,56 +34,40 @@ class GradeSummaryPresenter @Inject constructor(
} }
private fun loadData(semesterId: Int, forceRefresh: Boolean) { private fun loadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade summary started") flatResourceFlow {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh) averageProvider.getGradesDetailsWithAverage(student, semesterId, forceRefresh)
}.onEach { }
Timber.d("Loading grade summary status: ${it.status}, data: ${it.data != null}") .logResourceStatus("load grade summary", showData = true)
when (it.status) { .mapResourceData { createGradeSummaryItems(it) }
Status.LOADING -> { .onResourceData {
val items = createGradeSummaryItems(it.data.orEmpty()) view?.run {
if (items.isNotEmpty()) { enableSwipe(true)
Timber.i("Loading grade summary result: load cached data") showProgress(false)
view?.run { showErrorView(false)
enableSwipe(true) showContent(it.isNotEmpty())
showRefresh(true) showEmpty(it.isEmpty())
showProgress(false) updateData(it)
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) analytics.logEvent(
showProgress(false) "load_data",
enableSwipe(true) "type" to "grade_summary",
notifyParentDataLoaded(semesterId) "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) { private fun showErrorViewOnError(message: String, error: Throwable) {
@ -153,9 +134,9 @@ class GradeSummaryPresenter @Inject constructor(
private fun checkEmpty(gradeSummary: GradeSubject): Boolean { private fun checkEmpty(gradeSummary: GradeSubject): Boolean {
return gradeSummary.run { return gradeSummary.run {
summary.finalGrade.isBlank() summary.finalGrade.isBlank()
&& summary.predictedGrade.isBlank() && summary.predictedGrade.isBlank()
&& average == .0 && average == .0
&& points.isBlank() && points.isBlank()
} }
} }
} }

View File

@ -1,21 +1,13 @@
package io.github.wulkanowy.ui.modules.homework 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.db.entities.Homework
import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.*
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 kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -89,61 +81,59 @@ class HomeworkPresenter @Inject constructor(
flow { flow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
emit(semesterRepository.getCurrentSemester(student)) emit(semesterRepository.getCurrentSemester(student))
}.catch { }
Timber.i("Loading semester result: An exception occurred") .catch { Timber.i("Loading semester result: An exception occurred") }
}.onEach { .onEach {
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
currentDate = baseDate currentDate = baseDate
reloadNavigation() reloadNavigation()
}.launch("holidays") }
.launch("holidays")
} }
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading homework data started") Timber.i("Loading homework data started")
flowWithResourceIn { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) homeworkRepository.getHomework(
}.onEach { student = student,
when (it.status) { semester = semester,
Status.LOADING -> { start = currentDate,
if (!it.data.isNullOrEmpty()) { end = currentDate,
view?.run { forceRefresh = forceRefresh
enableSwipe(true) )
showRefresh(true) }
showProgress(false) .logResourceStatus("loading homework")
showContent(true) .mapResourceData { createHomeworkItem(it) }
updateData(createHomeworkItem(it.data)) .onResourceData {
} view?.run {
} enableSwipe(true)
} showProgress(false)
Status.SUCCESS -> { showErrorView(false)
Timber.i("Loading homework result: Success") showContent(it.isNotEmpty())
view?.apply { showEmpty(it.isEmpty())
updateData(createHomeworkItem(it.data!!)) updateData(it)
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!!)
} }
} }
}.afterLoading { .onResourceIntermediate { view?.showRefresh(true) }
view?.run { .onResourceSuccess {
showRefresh(false) analytics.logEvent(
showProgress(false) "load_data",
enableSwipe(true) "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) { private fun showErrorViewOnError(message: String, error: Throwable) {
@ -159,9 +149,10 @@ class HomeworkPresenter @Inject constructor(
private fun createHomeworkItem(items: List<Homework>): List<HomeworkItem<*>> { private fun createHomeworkItem(items: List<Homework>): List<HomeworkItem<*>> {
return items.groupBy { it.date }.toSortedMap().map { (date, exams) -> return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam -> listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed()
HomeworkItem(exam, HomeworkItem.ViewType.ITEM) .map { exam ->
} HomeworkItem(exam, HomeworkItem.ViewType.ITEM)
}
}.flatten() }.flatten()
} }
@ -184,8 +175,10 @@ class HomeworkPresenter @Inject constructor(
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays) showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + updateNavigationWeek(
currentDate.sunday.toFormattedString("dd.MM")) "${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
} }
} }
} }

View File

@ -1,15 +1,16 @@
package io.github.wulkanowy.ui.modules.homework.add 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.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.HomeworkRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.toLocalDate import io.github.wulkanowy.utils.toLocalDate
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -55,7 +56,7 @@ class HomeworkAddPresenter @Inject constructor(
} }
private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) { private fun saveHomework(subject: String, teacher: String, date: LocalDate, content: String) {
flowWithResource { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val entryDate = LocalDate.now() val entryDate = LocalDate.now()
@ -72,21 +73,15 @@ class HomeworkAddPresenter @Inject constructor(
attachments = emptyList(), attachments = emptyList(),
).apply { isAddedByUser = true } ).apply { isAddedByUser = true }
) )
}.onEach { }
when (it.status) { .logResourceStatus("homework insert")
Status.LOADING -> Timber.i("Homework insert start") .onResourceSuccess {
Status.SUCCESS -> { view?.run {
Timber.i("Homework insert: Success") showSuccessMessage()
view?.run { closeDialog()
showSuccessMessage()
closeDialog()
}
}
Status.ERROR -> {
Timber.i("Homework insert result: An exception occurred")
errorHandler.dispatch(it.error!!)
} }
} }
}.launch("add_homework") .onResourceError(errorHandler::dispatch)
.launch("add_homework")
} }
} }

View File

@ -1,15 +1,16 @@
package io.github.wulkanowy.ui.modules.homework.details 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.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.HomeworkRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -34,38 +35,26 @@ class HomeworkDetailsPresenter @Inject constructor(
} }
fun deleteHomework(homework: Homework) { fun deleteHomework(homework: Homework) {
flowWithResource { homeworkRepository.deleteHomework(homework) }.onEach { resourceFlow { homeworkRepository.deleteHomework(homework) }
when (it.status) { .logResourceStatus("homework delete")
Status.LOADING -> Timber.i("Homework delete start") .onResourceSuccess {
Status.SUCCESS -> { view?.run {
Timber.i("Homework delete: Success") showMessage(homeworkDeleteSuccess)
view?.run { closeDialog()
showMessage(homeworkDeleteSuccess)
closeDialog()
}
}
Status.ERROR -> {
Timber.i("Homework delete result: An exception occurred")
errorHandler.dispatch(it.error!!)
} }
} }
}.launch("delete") .onResourceError(errorHandler::dispatch)
.launch("delete")
} }
fun toggleDone(homework: Homework) { fun toggleDone(homework: Homework) {
flowWithResource { homeworkRepository.toggleDone(homework) }.onEach { resourceFlow { homeworkRepository.toggleDone(homework) }
when (it.status) { .logResourceStatus("homework details update")
Status.LOADING -> Timber.i("Homework details update start") .onResourceSuccess {
Status.SUCCESS -> { view?.updateMarkAsDoneLabel(homework.isDone)
Timber.i("Homework details update: Success") analytics.logEvent("homework_mark_as_done")
view?.updateMarkAsDoneLabel(homework.isDone)
analytics.logEvent("homework_mark_as_done")
}
Status.ERROR -> {
Timber.i("Homework details update result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
} }
}.launch("toggle") .onResourceError(errorHandler::dispatch)
.launch("toggle")
} }
} }

View File

@ -1,15 +1,16 @@
package io.github.wulkanowy.ui.modules.login.advanced 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.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.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -129,20 +130,20 @@ class LoginAdvancedPresenter @Inject constructor(
fun onSignInClick() { fun onSignInClick() {
if (!validateCredentials()) return if (!validateCredentials()) return
flowWithResource { getStudentsAppropriatesToLoginType() }.onEach { resourceFlow { getStudentsAppropriatesToLoginType() }
when (it.status) { .logResourceStatus("login")
Status.LOADING -> view?.run { .onEach {
Timber.i("Login started") when (it) {
hideSoftKeyboard() is Resource.Loading -> view?.run {
showProgress(true) hideSoftKeyboard()
showContent(false) showProgress(true)
} showContent(false)
Status.SUCCESS -> { }
Timber.i("Login result: Success") is Resource.Success -> {
analytics.logEvent( analytics.logEvent(
"registration_form", "registration_form",
"success" to true, "success" to true,
"students" to it.data!!.size, "students" to it.data.size,
"error" to "No error" "error" to "No error"
) )
val loginData = LoginData( val loginData = LoginData(
@ -154,23 +155,22 @@ class LoginAdvancedPresenter @Inject constructor(
0 -> view?.navigateToSymbol(loginData) 0 -> view?.navigateToSymbol(loginData)
else -> view?.navigateToStudentSelect(it.data) 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 -> { }.onResourceNotLoading {
Timber.i("Login result: An exception occurred") view?.apply {
analytics.logEvent( showProgress(false)
"registration_form", showContent(true)
"success" to false, "students" to -1,
"error" to it.error!!.message.ifNullOrBlank { "No message" }
)
loginErrorHandler.dispatch(it.error)
} }
} }.launch("login")
}.afterLoading {
view?.apply {
showProgress(false)
showContent(true)
}
}.launch("login")
} }
private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> { private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> {

View File

@ -1,16 +1,13 @@
package io.github.wulkanowy.ui.modules.login.form package io.github.wulkanowy.ui.modules.login.form
import androidx.core.net.toUri 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.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import java.net.URL import java.net.URL
import javax.inject.Inject import javax.inject.Inject
@ -75,7 +72,7 @@ class LoginFormPresenter @Inject constructor(
val username = view?.formUsernameValue.orEmpty().trim() val username = view?.formUsernameValue.orEmpty().trim()
if ("@" in username && "@vulcan" !in username) { 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("@") val usernameHost = username.substringAfter("@")
hosts[usernameHost]?.let { hosts[usernameHost]?.let {
@ -95,54 +92,54 @@ class LoginFormPresenter @Inject constructor(
if (!validateCredentials(email, password, host)) return if (!validateCredentials(email, password, host)) return
flowWithResource { resourceFlow {
studentRepository.getStudentsScrapper( studentRepository.getStudentsScrapper(
email = email, email = email,
password = password, password = password,
scrapperBaseUrl = host, scrapperBaseUrl = host,
symbol = symbol symbol = symbol
) )
}.onEach { }
when (it.status) { .logResourceStatus("login")
Status.LOADING -> view?.run { .onResourceLoading {
Timber.i("Login started") view?.run {
hideSoftKeyboard() hideSoftKeyboard()
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
Status.SUCCESS -> { }
Timber.i("Login result: Success") .onResourceSuccess {
analytics.logEvent( when (it.size) {
"registration_form", 0 -> view?.navigateToSymbol(LoginData(email, password, host))
"success" to true, else -> view?.navigateToStudentSelect(it)
"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)
}
} }
Status.ERROR -> { analytics.logEvent(
Timber.i("Login result: An exception occurred") "registration_form",
analytics.logEvent( "success" to true,
"registration_form", "students" to it.size,
"success" to false, "scrapperBaseUrl" to host,
"students" to -1, "error" to "No error"
"scrapperBaseUrl" to host, )
"error" to it.error!!.message.ifNullOrBlank { "No message" }) }
loginErrorHandler.dispatch(it.error) .onResourceNotLoading {
lastError = it.error view?.apply {
view?.showContact(true) showProgress(false)
showContent(true)
} }
} }
}.afterLoading { .onResourceError {
view?.apply { loginErrorHandler.dispatch(it)
showProgress(false) lastError = it
showContent(true) 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() { fun onFaqClick() {

View File

@ -1,12 +1,12 @@
package io.github.wulkanowy.ui.modules.login.recover 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.RecoverRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.utils.AnalyticsHelper 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 io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -57,24 +57,28 @@ class LoginRecoverPresenter @Inject constructor(
if (!validateInput(username, host)) return if (!validateInput(username, host)) return
flowWithResource { recoverRepository.getReCaptchaSiteKey(host, symbol.ifBlank { "Default" }) }.onEach { resourceFlow {
when (it.status) { recoverRepository.getReCaptchaSiteKey(
Status.LOADING -> view?.run { host,
symbol.ifBlank { "Default" })
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
hideSoftKeyboard() hideSoftKeyboard()
showRecoverForm(false) showRecoverForm(false)
showProgress(true) showProgress(true)
showErrorView(false) showErrorView(false)
showCaptcha(false) showCaptcha(false)
} }
Status.SUCCESS -> view?.run { is Resource.Success -> view?.run {
loadReCaptcha(url = it.data!!.first, siteKey = it.data.second) loadReCaptcha(url = it.data.first, siteKey = it.data.second)
showProgress(false) showProgress(false)
showErrorView(false) showErrorView(false)
showCaptcha(true) showCaptcha(true)
} }
Status.ERROR -> { is Resource.Error -> {
Timber.i("Obtain captcha site key result: An exception occurred") Timber.i("Obtain captcha site key result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
} }
} }
}.launch("captcha") }.launch("captcha")
@ -101,26 +105,43 @@ class LoginRecoverPresenter @Inject constructor(
val host = view?.recoverHostValue.orEmpty() val host = view?.recoverHostValue.orEmpty()
val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" } val symbol = view?.formHostSymbol.ifNullOrBlank { "Default" }
flowWithResource { recoverRepository.sendRecoverRequest(host, symbol, username, reCaptchaResponse) }.onEach { resourceFlow {
when (it.status) { recoverRepository.sendRecoverRequest(
Status.LOADING -> view?.run { host,
symbol,
username,
reCaptchaResponse
)
}.onEach {
when (it) {
is Resource.Loading -> view?.run {
showProgress(true) showProgress(true)
showRecoverForm(false) showRecoverForm(false)
showCaptcha(false) showCaptcha(false)
} }
Status.SUCCESS -> view?.run { is Resource.Success -> view?.run {
showSuccessView(true) showSuccessView(true)
setSuccessTitle(it.data!!.substringBefore(". ")) setSuccessTitle(it.data.substringBefore(". "))
setSuccessMessage(it.data.substringAfter(". ")) 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") Timber.i("Send recover request result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
analytics.logEvent("account_recover", "register" to host, "symbol" to symbol, "success" to false) analytics.logEvent(
"account_recover",
"register" to host,
"symbol" to symbol,
"success" to false
)
} }
} }
}.afterLoading { }.onResourceNotLoading {
view?.showProgress(false) view?.showProgress(false)
}.launch("verified") }.launch("verified")
} }

View File

@ -1,14 +1,15 @@
package io.github.wulkanowy.ui.modules.login.studentselect 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.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters 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.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.ifNullOrBlank import io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -66,16 +67,16 @@ class LoginStudentSelectPresenter @Inject constructor(
private fun loadData(studentsWithSemesters: List<StudentWithSemesters>) { private fun loadData(studentsWithSemesters: List<StudentWithSemesters>) {
resetSelectedState() resetSelectedState()
flowWithResource { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
when (it.status) { when (it) {
Status.LOADING -> Timber.d("Login student select students load started") is Resource.Loading -> Timber.d("Login student select students load started")
Status.SUCCESS -> view?.updateData(studentsWithSemesters.map { studentWithSemesters -> is Resource.Success -> view?.updateData(studentsWithSemesters.map { studentWithSemesters ->
studentWithSemesters to it.data!!.any { item -> studentWithSemesters to it.data.any { item ->
compareStudents(studentWithSemesters.student, item.student) compareStudents(studentWithSemesters.student, item.student)
} }
}) })
Status.ERROR -> { is Resource.Error -> {
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error)
lastError = it.error lastError = it.error
view?.updateData(studentsWithSemesters.map { student -> student to false }) view?.updateData(studentsWithSemesters.map { student -> student to false })
} }
@ -89,29 +90,27 @@ class LoginStudentSelectPresenter @Inject constructor(
} }
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) { private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
flowWithResource { studentRepository.saveStudents(studentsWithSemesters) } resourceFlow { studentRepository.saveStudents(studentsWithSemesters) }
.logResourceStatus("registration")
.onEach { .onEach {
when (it.status) { when (it) {
Status.LOADING -> view?.run { is Resource.Loading -> view?.run {
Timber.i("Registration started")
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
Status.SUCCESS -> { is Resource.Success -> {
Timber.i("Registration result: Success")
syncManager.startOneTimeSyncWorker(quiet = true) syncManager.startOneTimeSyncWorker(quiet = true)
view?.openMainView() view?.openMainView()
logRegisterEvent(studentsWithSemesters) logRegisterEvent(studentsWithSemesters)
} }
Status.ERROR -> { is Resource.Error -> {
Timber.i("Registration result: An exception occurred ")
view?.apply { view?.apply {
showProgress(false) showProgress(false)
showContent(true) showContent(true)
showContact(true) showContact(true)
} }
lastError = it.error lastError = it.error
loginErrorHandler.dispatch(it.error!!) loginErrorHandler.dispatch(it.error)
logRegisterEvent(studentsWithSemesters, it.error) logRegisterEvent(studentsWithSemesters, it.error)
} }
} }

View File

@ -1,13 +1,13 @@
package io.github.wulkanowy.ui.modules.login.symbol 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.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper 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 io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -45,7 +45,7 @@ class LoginSymbolPresenter @Inject constructor(
return return
} }
flowWithResource { resourceFlow {
studentRepository.getStudentsScrapper( studentRepository.getStudentsScrapper(
email = loginData.login, email = loginData.login,
password = loginData.password, password = loginData.password,
@ -53,15 +53,15 @@ class LoginSymbolPresenter @Inject constructor(
symbol = symbol, symbol = symbol,
) )
}.onEach { }.onEach {
when (it.status) { when (it) {
Status.LOADING -> view?.run { is Resource.Loading -> view?.run {
Timber.i("Login with symbol started") Timber.i("Login with symbol started")
hideSoftKeyboard() hideSoftKeyboard()
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
Status.SUCCESS -> { is Resource.Success -> {
when (it.data?.size) { when (it.data.size) {
0 -> { 0 -> {
Timber.i("Login with symbol result: Empty student list") Timber.i("Login with symbol result: Empty student list")
view?.run { view?.run {
@ -77,13 +77,13 @@ class LoginSymbolPresenter @Inject constructor(
analytics.logEvent( analytics.logEvent(
"registration_symbol", "registration_symbol",
"success" to true, "success" to true,
"students" to it.data!!.size, "students" to it.data.size,
"scrapperBaseUrl" to loginData.baseUrl, "scrapperBaseUrl" to loginData.baseUrl,
"symbol" to symbol, "symbol" to symbol,
"error" to "No error" "error" to "No error"
) )
} }
Status.ERROR -> { is Resource.Error -> {
Timber.i("Login with symbol result: An exception occurred") Timber.i("Login with symbol result: An exception occurred")
analytics.logEvent( analytics.logEvent(
"registration_symbol", "registration_symbol",
@ -91,14 +91,14 @@ class LoginSymbolPresenter @Inject constructor(
"students" to -1, "students" to -1,
"scrapperBaseUrl" to loginData.baseUrl, "scrapperBaseUrl" to loginData.baseUrl,
"symbol" to symbol, "symbol" to symbol,
"error" to it.error!!.message.ifNullOrBlank { "No message" } "error" to it.error.message.ifNullOrBlank { "No message" }
) )
loginErrorHandler.dispatch(it.error) loginErrorHandler.dispatch(it.error)
lastError = it.error lastError = it.error
view?.showContact(true) view?.showContact(true)
} }
} }
}.afterLoading { }.onResourceNotLoading {
view?.apply { view?.apply {
showProgress(false) showProgress(false)
showContent(true) showContent(true)

View File

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

View File

@ -1,24 +1,12 @@
package io.github.wulkanowy.ui.modules.luckynumber.history 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.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.afterLoading import kotlinx.coroutines.flow.*
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 timber.log.Timber import timber.log.Timber
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -52,55 +40,51 @@ class LuckyNumberHistoryPresenter @Inject constructor(
flow { flow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
emit(semesterRepository.getCurrentSemester(student)) emit(semesterRepository.getCurrentSemester(student))
}.catch { }
Timber.i("Loading semester result: An exception occurred") .catch { Timber.i("Loading semester result: An exception occurred") }
}.onEach { .onEach {
currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear) currentDate = currentDate.getLastSchoolDayIfHoliday(it.schoolYear)
reloadNavigation() reloadNavigation()
}.launch("holidays") }
.launch("holidays")
} }
private fun loadData() { private fun loadData() {
flowWithResource { flow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday) emitAll(
}.onEach { luckyNumberRepository.getLuckyNumberHistory(
when (it.status) { student = student,
Status.LOADING -> Timber.i("Loading lucky number history started") start = currentDate.monday,
Status.SUCCESS -> { end = currentDate.sunday
if (!it.data?.first().isNullOrEmpty()) { )
Timber.i("Loading lucky number result: Success") )
view?.apply { }
updateData(it.data!!.first()) .onEach {
showContent(true) if (!it.isNullOrEmpty()) {
showEmpty(false) view?.apply {
showErrorView(false) updateData(it)
showProgress(false) showContent(true)
} showEmpty(false)
analytics.logEvent( showErrorView(false)
"load_items", showProgress(false)
"type" to "lucky_number_history", }
"numbers" to it.data } else {
) view?.run {
} else { showContent(false)
Timber.i("Loading lucky number history result: No lucky numbers found") showEmpty(true)
view?.run { showErrorView(false)
showContent(false) showProgress(false)
showEmpty(true)
showErrorView(false)
}
} }
} }
Status.ERROR -> {
Timber.i("Loading lucky number history result: An exception occurred") analytics.logEvent(
errorHandler.dispatch(it.error!!) "load_items",
} "type" to "lucky_number_history",
)
} }
}.afterLoading { .catch { errorHandler.dispatch(it) }
view?.run { .launchIn(presenterScope)
showProgress(false)
}
}.launch()
} }
private fun showErrorViewOnError(message: String, error: Throwable) { private fun showErrorViewOnError(message: String, error: Throwable) {
@ -143,8 +127,10 @@ class LuckyNumberHistoryPresenter @Inject constructor(
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(7).isHolidays) showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays) showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + updateNavigationWeek(
currentDate.sunday.toFormattedString("dd.MM")) "${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
)
} }
} }

View File

@ -1,14 +1,14 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget 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.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository 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.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler 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.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -47,16 +47,15 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { studentRepository.getSavedStudents(false) }.onEach { resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
when (it.status) { when (it) {
Status.LOADING -> Timber.d("Lucky number widget configure students data load") is Resource.Loading -> Timber.d("Lucky number widget configure students data load")
Status.SUCCESS -> { is Resource.Success -> {
val selectedStudentId = appWidgetId?.let { id -> val selectedStudentId = appWidgetId?.let { id ->
sharedPref.getLong(getStudentWidgetKey(id), 0) sharedPref.getLong(getStudentWidgetKey(id), 0)
} ?: -1 } ?: -1
when { when {
it.data!!.isEmpty() -> view?.openLoginView() it.data.isEmpty() -> view?.openLoginView()
it.data.size == 1 -> { it.data.size == 1 -> {
selectedStudent = it.data.single().student selectedStudent = it.data.single().student
view?.showThemeDialog() view?.showThemeDialog()
@ -64,7 +63,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
else -> view?.updateData(it.data, selectedStudentId) else -> view?.updateData(it.data, selectedStudentId)
} }
} }
Status.ERROR -> errorHandler.dispatch(it.error!!) is Resource.Error -> errorHandler.dispatch(it.error)
} }
}.launch() }.launch()
} }

View File

@ -13,14 +13,17 @@ import android.view.View.VISIBLE
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R 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.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.StudentRepository 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.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.toFirstResult
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -66,12 +69,16 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE 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 = val remoteView =
RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context))
.apply { .apply {
setTextViewText( setTextViewText(
R.id.luckyNumberWidgetNumber, R.id.luckyNumberWidgetNumber,
luckyNumber?.luckyNumber?.toString() ?: "#" luckyNumber.dataOrNull?.toString() ?: "#"
) )
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
} }
@ -167,14 +174,17 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
else -> null else -> null
} }
currentStudent?.let { if (currentStudent != null) {
luckyNumberRepository.getLuckyNumber(it, false).toFirstResult().data luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
.toFirstResult()
} else {
Resource.Success<LuckyNumber?>(null)
} }
} catch (e: Exception) { } catch (e: Exception) {
if (e.cause !is NoCurrentStudentException) { if (e.cause !is NoCurrentStudentException) {
Timber.e(e, "An error has occurred in lucky number provider") 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.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.*
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 timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -169,8 +160,12 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
.setIcon(R.drawable.ic_main_more) .setIcon(R.drawable.ic_main_more)
} }
selectedItemId = startMenuIndex selectedItemId = startMenuIndex
setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) } setOnItemSelectedListener {
setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) } 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, caller: PreferenceFragmentCompat,
pref: Preference pref: Preference
): Boolean { ): Boolean {
val fragment = val fragment = supportFragmentManager.fragmentFactory.instantiate(
supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment) classLoader,
pref.fragment.toString()
)
pushView(fragment) pushView(fragment)
return true return true
} }

View File

@ -1,9 +1,12 @@
package io.github.wulkanowy.ui.modules.main 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.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.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.BaseView 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.schoolandteachers.SchoolAndTeachersView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@ -75,20 +76,14 @@ class MainPresenter @Inject constructor(
return return
} }
flowWithResource { studentRepository.getSavedStudents(false) } resourceFlow { studentRepository.getSavedStudents(false) }
.onEach { resource -> .logResourceStatus("load student avatar")
when (resource.status) { .onResourceSuccess {
Status.LOADING -> Timber.i("Loading student avatar data started") studentsWitSemesters = it
Status.SUCCESS -> { showCurrentStudentAvatar()
studentsWitSemesters = resource.data }
showCurrentStudentAvatar() .onResourceError(errorHandler::dispatch)
} .launch("avatar")
Status.ERROR -> {
Timber.i("Loading student avatar result: An exception occurred")
errorHandler.dispatch(resource.error!!)
}
}
}.launch("avatar")
} }
fun onViewChange(destinationView: BaseView) { fun onViewChange(destinationView: BaseView) {

View File

@ -4,12 +4,14 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.*
import io.github.wulkanowy.data.enums.MessageFolder.SENT
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.databinding.FragmentMessageBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
@ -78,7 +80,6 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
} }
binding.messageTabLayout.elevation = requireContext().dpToPx(4f) binding.messageTabLayout.elevation = requireContext().dpToPx(4f)
binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() } binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() }
} }
@ -93,12 +94,37 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showNewMessage(show: Boolean) {
binding.openSendMessageButton.run {
if (show) show() else hide()
}
}
override fun showTabLayout(show: Boolean) {
binding.messageTabLayout.isVisible = show
with(binding.messageViewPager) {
isUserInputEnabled = show
updateLayoutParams<ViewGroup.MarginLayoutParams> {
updateMargins(top = if (show) requireContext().dpToPx(48f).toInt() else 0)
}
}
}
fun onChildFragmentShowActionMode(show: Boolean) {
presenter.onChildViewShowActionMode(show)
}
fun onChildFragmentLoaded() { fun onChildFragmentLoaded() {
presenter.onChildViewLoaded() presenter.onChildViewLoaded()
} }
override fun notifyChildMessageDeleted(tabId: Int) { fun onChildFragmentShowNewMessage(show: Boolean) {
(pagerAdapter.getFragmentInstance(tabId) as? MessageTabFragment)?.onParentDeleteMessage() presenter.onChildViewShowNewMessage(show)
}
fun onFragmentChanged() {
presenter.onFragmentChanged()
} }
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) { override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
@ -106,6 +132,13 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
?.onParentLoadData(forceRefresh) ?.onParentLoadData(forceRefresh)
} }
override fun notifyChildrenFinishActionMode() {
repeat(3) {
(pagerAdapter.getFragmentInstance(it) as? MessageTabFragment)
?.onParentFinishActionMode()
}
}
override fun openSendMessage() { override fun openSendMessage() {
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) } context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) }
} }

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